Merge pull request #4292 from bunnei/mii-rewrite
hle: service: mii: Rewrite service to properly support creation of random and default miis.
This commit is contained in:
		| @@ -398,10 +398,13 @@ add_library(core STATIC | |||||||
|     hle/service/lm/manager.h |     hle/service/lm/manager.h | ||||||
|     hle/service/mig/mig.cpp |     hle/service/mig/mig.cpp | ||||||
|     hle/service/mig/mig.h |     hle/service/mig/mig.h | ||||||
|  |     hle/service/mii/manager.cpp | ||||||
|  |     hle/service/mii/manager.h | ||||||
|     hle/service/mii/mii.cpp |     hle/service/mii/mii.cpp | ||||||
|     hle/service/mii/mii.h |     hle/service/mii/mii.h | ||||||
|     hle/service/mii/mii_manager.cpp |     hle/service/mii/raw_data.cpp | ||||||
|     hle/service/mii/mii_manager.h |     hle/service/mii/raw_data.h | ||||||
|  |     hle/service/mii/types.h | ||||||
|     hle/service/mm/mm_u.cpp |     hle/service/mm/mm_u.cpp | ||||||
|     hle/service/mm/mm_u.h |     hle/service/mm/mm_u.h | ||||||
|     hle/service/ncm/ncm.cpp |     hle/service/ncm/ncm.cpp | ||||||
|   | |||||||
							
								
								
									
										483
									
								
								src/core/hle/service/mii/manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										483
									
								
								src/core/hle/service/mii/manager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,483 @@ | |||||||
|  | // Copyright 2020 yuzu emulator team | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <cstring> | ||||||
|  | #include <random> | ||||||
|  |  | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "common/string_util.h" | ||||||
|  |  | ||||||
|  | #include "core/hle/service/acc/profile_manager.h" | ||||||
|  | #include "core/hle/service/mii/manager.h" | ||||||
|  | #include "core/hle/service/mii/raw_data.h" | ||||||
|  | #include "core/hle/service/mii/types.h" | ||||||
|  |  | ||||||
|  | namespace Service::Mii { | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; | ||||||
|  |  | ||||||
|  | constexpr std::size_t DefaultMiiCount{sizeof(RawData::DefaultMii) / sizeof(DefaultMii)}; | ||||||
|  |  | ||||||
|  | constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; | ||||||
|  | constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7}; | ||||||
|  | constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13}; | ||||||
|  | constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23}; | ||||||
|  | constexpr std::array<u8, 7> GlassesColorLookup{8, 14, 15, 16, 17, 18, 0}; | ||||||
|  | constexpr std::array<u8, 62> EyeRotateLookup{ | ||||||
|  |     {0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, | ||||||
|  |      0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, | ||||||
|  |      0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, | ||||||
|  |      0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}}; | ||||||
|  | constexpr std::array<u8, 24> EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, | ||||||
|  |                                                   0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06, | ||||||
|  |                                                   0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}}; | ||||||
|  |  | ||||||
|  | template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize> | ||||||
|  | std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) { | ||||||
|  |     std::array<T, DestArraySize> out{}; | ||||||
|  |     std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize)); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) { | ||||||
|  |     MiiStoreBitFields bf; | ||||||
|  |     std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields)); | ||||||
|  |     MiiInfo info{}; | ||||||
|  |     info.name = ResizeArray<char16_t, 10, 11>(data.data.name); | ||||||
|  |     info.uuid = data.data.uuid; | ||||||
|  |     info.font_region = static_cast<u8>(bf.font_region.Value()); | ||||||
|  |     info.favorite_color = static_cast<u8>(bf.favorite_color.Value()); | ||||||
|  |     info.gender = static_cast<u8>(bf.gender.Value()); | ||||||
|  |     info.height = static_cast<u8>(bf.height.Value()); | ||||||
|  |     info.build = static_cast<u8>(bf.build.Value()); | ||||||
|  |     info.type = static_cast<u8>(bf.type.Value()); | ||||||
|  |     info.region_move = static_cast<u8>(bf.region_move.Value()); | ||||||
|  |     info.faceline_type = static_cast<u8>(bf.faceline_type.Value()); | ||||||
|  |     info.faceline_color = static_cast<u8>(bf.faceline_color.Value()); | ||||||
|  |     info.faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()); | ||||||
|  |     info.faceline_make = static_cast<u8>(bf.faceline_makeup.Value()); | ||||||
|  |     info.hair_type = static_cast<u8>(bf.hair_type.Value()); | ||||||
|  |     info.hair_color = static_cast<u8>(bf.hair_color.Value()); | ||||||
|  |     info.hair_flip = static_cast<u8>(bf.hair_flip.Value()); | ||||||
|  |     info.eye_type = static_cast<u8>(bf.eye_type.Value()); | ||||||
|  |     info.eye_color = static_cast<u8>(bf.eye_color.Value()); | ||||||
|  |     info.eye_scale = static_cast<u8>(bf.eye_scale.Value()); | ||||||
|  |     info.eye_aspect = static_cast<u8>(bf.eye_aspect.Value()); | ||||||
|  |     info.eye_rotate = static_cast<u8>(bf.eye_rotate.Value()); | ||||||
|  |     info.eye_x = static_cast<u8>(bf.eye_x.Value()); | ||||||
|  |     info.eye_y = static_cast<u8>(bf.eye_y.Value()); | ||||||
|  |     info.eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()); | ||||||
|  |     info.eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()); | ||||||
|  |     info.eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()); | ||||||
|  |     info.eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()); | ||||||
|  |     info.eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()); | ||||||
|  |     info.eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()); | ||||||
|  |     info.eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3); | ||||||
|  |     info.nose_type = static_cast<u8>(bf.nose_type.Value()); | ||||||
|  |     info.nose_scale = static_cast<u8>(bf.nose_scale.Value()); | ||||||
|  |     info.nose_y = static_cast<u8>(bf.nose_y.Value()); | ||||||
|  |     info.mouth_type = static_cast<u8>(bf.mouth_type.Value()); | ||||||
|  |     info.mouth_color = static_cast<u8>(bf.mouth_color.Value()); | ||||||
|  |     info.mouth_scale = static_cast<u8>(bf.mouth_scale.Value()); | ||||||
|  |     info.mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()); | ||||||
|  |     info.mouth_y = static_cast<u8>(bf.mouth_y.Value()); | ||||||
|  |     info.beard_color = static_cast<u8>(bf.beard_color.Value()); | ||||||
|  |     info.beard_type = static_cast<u8>(bf.beard_type.Value()); | ||||||
|  |     info.mustache_type = static_cast<u8>(bf.mustache_type.Value()); | ||||||
|  |     info.mustache_scale = static_cast<u8>(bf.mustache_scale.Value()); | ||||||
|  |     info.mustache_y = static_cast<u8>(bf.mustache_y.Value()); | ||||||
|  |     info.glasses_type = static_cast<u8>(bf.glasses_type.Value()); | ||||||
|  |     info.glasses_color = static_cast<u8>(bf.glasses_color.Value()); | ||||||
|  |     info.glasses_scale = static_cast<u8>(bf.glasses_scale.Value()); | ||||||
|  |     info.glasses_y = static_cast<u8>(bf.glasses_y.Value()); | ||||||
|  |     info.mole_type = static_cast<u8>(bf.mole_type.Value()); | ||||||
|  |     info.mole_scale = static_cast<u8>(bf.mole_scale.Value()); | ||||||
|  |     info.mole_x = static_cast<u8>(bf.mole_x.Value()); | ||||||
|  |     info.mole_y = static_cast<u8>(bf.mole_y.Value()); | ||||||
|  |     return info; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | u16 GenerateCrc16(const void* data, std::size_t size) { | ||||||
|  |     s32 crc{}; | ||||||
|  |     for (int i = 0; i < size; i++) { | ||||||
|  |         crc ^= reinterpret_cast<const u8*>(data)[i] << 8; | ||||||
|  |         for (int j = 0; j < 8; j++) { | ||||||
|  |             crc <<= 1; | ||||||
|  |             if ((crc & 0x10000) != 0) { | ||||||
|  |                 crc = (crc ^ 0x1021) & 0xFFFF; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return Common::swap16(static_cast<u16>(crc)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Common::UUID GenerateValidUUID() { | ||||||
|  |     auto uuid{Common::UUID::Generate()}; | ||||||
|  |  | ||||||
|  |     // Bit 7 must be set, and bit 6 unset for the UUID to be valid | ||||||
|  |     uuid.uuid[1] &= 0xFFFFFFFFFFFFFF3FULL; | ||||||
|  |     uuid.uuid[1] |= 0x0000000000000080ULL; | ||||||
|  |  | ||||||
|  |     return uuid; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> | ||||||
|  | T GetRandomValue(T min, T max) { | ||||||
|  |     std::random_device device; | ||||||
|  |     std::mt19937 gen(device()); | ||||||
|  |     std::uniform_int_distribution<u64> distribution(0, static_cast<u64>(max)); | ||||||
|  |     return static_cast<T>(distribution(gen)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> | ||||||
|  | T GetRandomValue(T max) { | ||||||
|  |     return GetRandomValue<T>({}, max); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename T> | ||||||
|  | T GetArrayValue(const u8* data, std::size_t index) { | ||||||
|  |     T result{}; | ||||||
|  |     std::memcpy(&result, &data[index * sizeof(T)], sizeof(T)); | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) { | ||||||
|  |     MiiStoreBitFields bf{}; | ||||||
|  |  | ||||||
|  |     if (gender == Gender::All) { | ||||||
|  |         gender = GetRandomValue<Gender>(Gender::Maximum); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bf.gender.Assign(gender); | ||||||
|  |     bf.favorite_color.Assign(GetRandomValue<u8>(11)); | ||||||
|  |     bf.region_move.Assign(0); | ||||||
|  |     bf.font_region.Assign(FontRegion::Standard); | ||||||
|  |     bf.type.Assign(0); | ||||||
|  |     bf.height.Assign(64); | ||||||
|  |     bf.build.Assign(64); | ||||||
|  |  | ||||||
|  |     if (age == Age::All) { | ||||||
|  |         const auto temp{GetRandomValue<int>(10)}; | ||||||
|  |         if (temp >= 8) { | ||||||
|  |             age = Age::Old; | ||||||
|  |         } else if (temp >= 4) { | ||||||
|  |             age = Age::Normal; | ||||||
|  |         } else { | ||||||
|  |             age = Age::Young; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (race == Race::All) { | ||||||
|  |         const auto temp{GetRandomValue<int>(10)}; | ||||||
|  |         if (temp >= 8) { | ||||||
|  |             race = Race::Black; | ||||||
|  |         } else if (temp >= 4) { | ||||||
|  |             race = Race::White; | ||||||
|  |         } else { | ||||||
|  |             race = Race::Asian; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     u32 axis_y{}; | ||||||
|  |     if (gender == Gender::Female && age == Age::Young) { | ||||||
|  |         axis_y = GetRandomValue<u32>(3); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const std::size_t index{3 * static_cast<std::size_t>(age) + | ||||||
|  |                             9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)}; | ||||||
|  |  | ||||||
|  |     const auto faceline_type_info{ | ||||||
|  |         GetArrayValue<RandomMiiData4>(&RawData::RandomMiiFaceline[0], index)}; | ||||||
|  |     const auto faceline_color_info{GetArrayValue<RandomMiiData3>( | ||||||
|  |         RawData::RandomMiiFacelineColor.data(), | ||||||
|  |         3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))}; | ||||||
|  |     const auto faceline_wrinkle_info{ | ||||||
|  |         GetArrayValue<RandomMiiData4>(RawData::RandomMiiFacelineWrinkle.data(), index)}; | ||||||
|  |     const auto faceline_makeup_info{ | ||||||
|  |         GetArrayValue<RandomMiiData4>(RawData::RandomMiiFacelineMakeup.data(), index)}; | ||||||
|  |     const auto hair_type_info{ | ||||||
|  |         GetArrayValue<RandomMiiData4>(RawData::RandomMiiHairType.data(), index)}; | ||||||
|  |     const auto hair_color_info{GetArrayValue<RandomMiiData3>(RawData::RandomMiiHairColor.data(), | ||||||
|  |                                                              3 * static_cast<std::size_t>(race) + | ||||||
|  |                                                                  static_cast<std::size_t>(age))}; | ||||||
|  |     const auto eye_type_info{ | ||||||
|  |         GetArrayValue<RandomMiiData4>(RawData::RandomMiiEyeType.data(), index)}; | ||||||
|  |     const auto eye_color_info{GetArrayValue<RandomMiiData2>(RawData::RandomMiiEyeColor.data(), | ||||||
|  |                                                             static_cast<std::size_t>(race))}; | ||||||
|  |     const auto eyebrow_type_info{ | ||||||
|  |         GetArrayValue<RandomMiiData4>(RawData::RandomMiiEyebrowType.data(), index)}; | ||||||
|  |     const auto nose_type_info{ | ||||||
|  |         GetArrayValue<RandomMiiData4>(RawData::RandomMiiNoseType.data(), index)}; | ||||||
|  |     const auto mouth_type_info{ | ||||||
|  |         GetArrayValue<RandomMiiData4>(RawData::RandomMiiMouthType.data(), index)}; | ||||||
|  |     const auto glasses_type_info{GetArrayValue<RandomMiiData2>(RawData::RandomMiiGlassType.data(), | ||||||
|  |                                                                static_cast<std::size_t>(age))}; | ||||||
|  |  | ||||||
|  |     bf.faceline_type.Assign( | ||||||
|  |         faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]); | ||||||
|  |     bf.faceline_color.Assign( | ||||||
|  |         faceline_color_info.values[GetRandomValue<std::size_t>(faceline_color_info.values_count)]); | ||||||
|  |     bf.faceline_wrinkle.Assign( | ||||||
|  |         faceline_wrinkle_info | ||||||
|  |             .values[GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]); | ||||||
|  |     bf.faceline_makeup.Assign( | ||||||
|  |         faceline_makeup_info | ||||||
|  |             .values[GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]); | ||||||
|  |  | ||||||
|  |     bf.hair_type.Assign( | ||||||
|  |         hair_type_info.values[GetRandomValue<std::size_t>(hair_type_info.values_count)]); | ||||||
|  |     bf.hair_color.Assign( | ||||||
|  |         HairColorLookup[hair_color_info | ||||||
|  |                             .values[GetRandomValue<std::size_t>(hair_color_info.values_count)]]); | ||||||
|  |     bf.hair_flip.Assign(GetRandomValue<HairFlip>(HairFlip::Maximum)); | ||||||
|  |  | ||||||
|  |     bf.eye_type.Assign( | ||||||
|  |         eye_type_info.values[GetRandomValue<std::size_t>(eye_type_info.values_count)]); | ||||||
|  |  | ||||||
|  |     const auto eye_rotate_1{gender != Gender::Male ? 4 : 2}; | ||||||
|  |     const auto eye_rotate_2{gender != Gender::Male ? 3 : 4}; | ||||||
|  |     const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2}; | ||||||
|  |     const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]}; | ||||||
|  |  | ||||||
|  |     bf.eye_color.Assign( | ||||||
|  |         EyeColorLookup[eye_color_info | ||||||
|  |                            .values[GetRandomValue<std::size_t>(eye_color_info.values_count)]]); | ||||||
|  |     bf.eye_scale.Assign(4); | ||||||
|  |     bf.eye_aspect.Assign(3); | ||||||
|  |     bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate); | ||||||
|  |     bf.eye_x.Assign(2); | ||||||
|  |     bf.eye_y.Assign(axis_y + 12); | ||||||
|  |  | ||||||
|  |     bf.eyebrow_type.Assign( | ||||||
|  |         eyebrow_type_info.values[GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); | ||||||
|  |  | ||||||
|  |     const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; | ||||||
|  |     const auto eyebrow_y{race == Race::Asian ? 9 : 10}; | ||||||
|  |     const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6}; | ||||||
|  |     const auto eyebrow_rotate{ | ||||||
|  |         32 - EyebrowRotateLookup[static_cast<std::size_t>(bf.eyebrow_type.Value())]}; | ||||||
|  |  | ||||||
|  |     bf.eyebrow_color.Assign(bf.hair_color); | ||||||
|  |     bf.eyebrow_scale.Assign(4); | ||||||
|  |     bf.eyebrow_aspect.Assign(3); | ||||||
|  |     bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate); | ||||||
|  |     bf.eyebrow_x.Assign(2); | ||||||
|  |     bf.eyebrow_y.Assign(axis_y + eyebrow_y); | ||||||
|  |  | ||||||
|  |     const auto nose_scale{gender == Gender::Female ? 3 : 4}; | ||||||
|  |  | ||||||
|  |     bf.nose_type.Assign( | ||||||
|  |         nose_type_info.values[GetRandomValue<std::size_t>(nose_type_info.values_count)]); | ||||||
|  |     bf.nose_scale.Assign(nose_scale); | ||||||
|  |     bf.nose_y.Assign(axis_y + 9); | ||||||
|  |  | ||||||
|  |     const auto mouth_color{gender == Gender::Female ? GetRandomValue<int>(4) : 0}; | ||||||
|  |  | ||||||
|  |     bf.mouth_type.Assign( | ||||||
|  |         mouth_type_info.values[GetRandomValue<std::size_t>(mouth_type_info.values_count)]); | ||||||
|  |     bf.mouth_color.Assign(MouthColorLookup[mouth_color]); | ||||||
|  |     bf.mouth_scale.Assign(4); | ||||||
|  |     bf.mouth_aspect.Assign(3); | ||||||
|  |     bf.mouth_y.Assign(axis_y + 13); | ||||||
|  |  | ||||||
|  |     bf.beard_color.Assign(bf.hair_color); | ||||||
|  |     bf.mustache_scale.Assign(4); | ||||||
|  |  | ||||||
|  |     if (gender == Gender::Male && age != Age::Young && GetRandomValue<int>(10) < 2) { | ||||||
|  |         const auto mustache_and_beard_flag{ | ||||||
|  |             GetRandomValue<BeardAndMustacheFlag>(BeardAndMustacheFlag::All)}; | ||||||
|  |  | ||||||
|  |         auto beard_type{BeardType::None}; | ||||||
|  |         auto mustache_type{MustacheType::None}; | ||||||
|  |  | ||||||
|  |         if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) == | ||||||
|  |             BeardAndMustacheFlag::Beard) { | ||||||
|  |             beard_type = GetRandomValue<BeardType>(BeardType::Beard1, BeardType::Beard5); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) == | ||||||
|  |             BeardAndMustacheFlag::Mustache) { | ||||||
|  |             mustache_type = | ||||||
|  |                 GetRandomValue<MustacheType>(MustacheType::Mustache1, MustacheType::Mustache5); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         bf.mustache_type.Assign(mustache_type); | ||||||
|  |         bf.beard_type.Assign(beard_type); | ||||||
|  |         bf.mustache_y.Assign(10); | ||||||
|  |     } else { | ||||||
|  |         bf.mustache_type.Assign(MustacheType::None); | ||||||
|  |         bf.beard_type.Assign(BeardType::None); | ||||||
|  |         bf.mustache_y.Assign(axis_y + 10); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const auto glasses_type_start{GetRandomValue<std::size_t>(100)}; | ||||||
|  |     u8 glasses_type{}; | ||||||
|  |     while (glasses_type_start < glasses_type_info.values[glasses_type]) { | ||||||
|  |         if (++glasses_type >= glasses_type_info.values_count) { | ||||||
|  |             UNREACHABLE(); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bf.glasses_type.Assign(glasses_type); | ||||||
|  |     bf.glasses_color.Assign(GlassesColorLookup[0]); | ||||||
|  |     bf.glasses_scale.Assign(4); | ||||||
|  |     bf.glasses_y.Assign(axis_y + 10); | ||||||
|  |  | ||||||
|  |     bf.mole_type.Assign(0); | ||||||
|  |     bf.mole_scale.Assign(4); | ||||||
|  |     bf.mole_x.Assign(2); | ||||||
|  |     bf.mole_y.Assign(20); | ||||||
|  |  | ||||||
|  |     return {DefaultMiiName, bf, user_id}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) { | ||||||
|  |     MiiStoreBitFields bf{}; | ||||||
|  |  | ||||||
|  |     bf.font_region.Assign(info.font_region); | ||||||
|  |     bf.favorite_color.Assign(info.favorite_color); | ||||||
|  |     bf.gender.Assign(info.gender); | ||||||
|  |     bf.height.Assign(info.height); | ||||||
|  |     bf.build.Assign(info.weight); | ||||||
|  |     bf.type.Assign(info.type); | ||||||
|  |     bf.region_move.Assign(info.region); | ||||||
|  |     bf.faceline_type.Assign(info.face_type); | ||||||
|  |     bf.faceline_color.Assign(info.face_color); | ||||||
|  |     bf.faceline_wrinkle.Assign(info.face_wrinkle); | ||||||
|  |     bf.faceline_makeup.Assign(info.face_makeup); | ||||||
|  |     bf.hair_type.Assign(info.hair_type); | ||||||
|  |     bf.hair_color.Assign(HairColorLookup[info.hair_color]); | ||||||
|  |     bf.hair_flip.Assign(static_cast<HairFlip>(info.hair_flip)); | ||||||
|  |     bf.eye_type.Assign(info.eye_type); | ||||||
|  |     bf.eye_color.Assign(EyeColorLookup[info.eye_color]); | ||||||
|  |     bf.eye_scale.Assign(info.eye_scale); | ||||||
|  |     bf.eye_aspect.Assign(info.eye_aspect); | ||||||
|  |     bf.eye_rotate.Assign(info.eye_rotate); | ||||||
|  |     bf.eye_x.Assign(info.eye_x); | ||||||
|  |     bf.eye_y.Assign(info.eye_y); | ||||||
|  |     bf.eyebrow_type.Assign(info.eyebrow_type); | ||||||
|  |     bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]); | ||||||
|  |     bf.eyebrow_scale.Assign(info.eyebrow_scale); | ||||||
|  |     bf.eyebrow_aspect.Assign(info.eyebrow_aspect); | ||||||
|  |     bf.eyebrow_rotate.Assign(info.eyebrow_rotate); | ||||||
|  |     bf.eyebrow_x.Assign(info.eyebrow_x); | ||||||
|  |     bf.eyebrow_y.Assign(info.eyebrow_y - 3); | ||||||
|  |     bf.nose_type.Assign(info.nose_type); | ||||||
|  |     bf.nose_scale.Assign(info.nose_scale); | ||||||
|  |     bf.nose_y.Assign(info.nose_y); | ||||||
|  |     bf.mouth_type.Assign(info.mouth_type); | ||||||
|  |     bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]); | ||||||
|  |     bf.mouth_scale.Assign(info.mouth_scale); | ||||||
|  |     bf.mouth_aspect.Assign(info.mouth_aspect); | ||||||
|  |     bf.mouth_y.Assign(info.mouth_y); | ||||||
|  |     bf.beard_color.Assign(HairColorLookup[info.beard_color]); | ||||||
|  |     bf.beard_type.Assign(static_cast<BeardType>(info.beard_type)); | ||||||
|  |     bf.mustache_type.Assign(static_cast<MustacheType>(info.mustache_type)); | ||||||
|  |     bf.mustache_scale.Assign(info.mustache_scale); | ||||||
|  |     bf.mustache_y.Assign(info.mustache_y); | ||||||
|  |     bf.glasses_type.Assign(info.glasses_type); | ||||||
|  |     bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]); | ||||||
|  |     bf.glasses_scale.Assign(info.glasses_scale); | ||||||
|  |     bf.glasses_y.Assign(info.glasses_y); | ||||||
|  |     bf.mole_type.Assign(info.mole_type); | ||||||
|  |     bf.mole_scale.Assign(info.mole_scale); | ||||||
|  |     bf.mole_x.Assign(info.mole_x); | ||||||
|  |     bf.mole_y.Assign(info.mole_y); | ||||||
|  |  | ||||||
|  |     return {DefaultMiiName, bf, user_id}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace | ||||||
|  |  | ||||||
|  | MiiStoreData::MiiStoreData() = default; | ||||||
|  |  | ||||||
|  | MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields, | ||||||
|  |                            const Common::UUID& user_id) { | ||||||
|  |     data.name = name; | ||||||
|  |     data.uuid = GenerateValidUUID(); | ||||||
|  |  | ||||||
|  |     std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields)); | ||||||
|  |     data_crc = GenerateCrc16(data.data.data(), sizeof(data)); | ||||||
|  |     device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {} | ||||||
|  |  | ||||||
|  | bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) { | ||||||
|  |     if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const bool result{current_update_counter != update_counter}; | ||||||
|  |  | ||||||
|  |     current_update_counter = update_counter; | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MiiManager::IsFullDatabase() const { | ||||||
|  |     // TODO(bunnei): We don't implement the Mii database, so it cannot be full | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | u32 MiiManager::GetCount(SourceFlag source_flag) const { | ||||||
|  |     u32 count{}; | ||||||
|  |     if ((source_flag & SourceFlag::Database) != SourceFlag::None) { | ||||||
|  |         // TODO(bunnei): We don't implement the Mii database, but when we do, update this | ||||||
|  |         count += 0; | ||||||
|  |     } | ||||||
|  |     if ((source_flag & SourceFlag::Default) != SourceFlag::None) { | ||||||
|  |         count += DefaultMiiCount; | ||||||
|  |     } | ||||||
|  |     return count; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info, | ||||||
|  |                                             SourceFlag source_flag) { | ||||||
|  |     if ((source_flag & SourceFlag::Database) == SourceFlag::None) { | ||||||
|  |         return ERROR_CANNOT_FIND_ENTRY; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // TODO(bunnei): We don't implement the Mii database, so we can't have an entry | ||||||
|  |     return ERROR_CANNOT_FIND_ENTRY; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { | ||||||
|  |     return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MiiInfo MiiManager::BuildDefault(std::size_t index) { | ||||||
|  |     return ConvertStoreDataToInfo(BuildDefaultStoreData( | ||||||
|  |         GetArrayValue<DefaultMii>(RawData::DefaultMii.data(), index), user_id)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) { | ||||||
|  |     std::vector<MiiInfoElement> result; | ||||||
|  |  | ||||||
|  |     if ((source_flag & SourceFlag::Default) == SourceFlag::None) { | ||||||
|  |         return MakeResult(std::move(result)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     for (std::size_t index = 0; index < DefaultMiiCount; index++) { | ||||||
|  |         result.emplace_back(BuildDefault(index), Source::Default); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return MakeResult(std::move(result)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ResultCode MiiManager::GetIndex([[maybe_unused]] const MiiInfo& info, u32& index) { | ||||||
|  |     constexpr u32 INVALID_INDEX{0xFFFFFFFF}; | ||||||
|  |  | ||||||
|  |     index = INVALID_INDEX; | ||||||
|  |  | ||||||
|  |     // TODO(bunnei): We don't implement the Mii database, so we can't have an index | ||||||
|  |     return ERROR_CANNOT_FIND_ENTRY; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace Service::Mii | ||||||
							
								
								
									
										331
									
								
								src/core/hle/service/mii/manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								src/core/hle/service/mii/manager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,331 @@ | |||||||
|  | // Copyright 2020 yuzu emulator team | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "common/bit_field.h" | ||||||
|  | #include "common/common_funcs.h" | ||||||
|  | #include "common/uuid.h" | ||||||
|  | #include "core/hle/result.h" | ||||||
|  | #include "core/hle/service/mii/types.h" | ||||||
|  |  | ||||||
|  | namespace Service::Mii { | ||||||
|  |  | ||||||
|  | enum class Source : u32 { | ||||||
|  |     Database = 0, | ||||||
|  |     Default = 1, | ||||||
|  |     Account = 2, | ||||||
|  |     Friend = 3, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class SourceFlag : u32 { | ||||||
|  |     None = 0, | ||||||
|  |     Database = 1 << 0, | ||||||
|  |     Default = 1 << 1, | ||||||
|  | }; | ||||||
|  | DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); | ||||||
|  |  | ||||||
|  | struct MiiInfo { | ||||||
|  |     Common::UUID uuid{Common::INVALID_UUID}; | ||||||
|  |     std::array<char16_t, 11> name{}; | ||||||
|  |     u8 font_region{}; | ||||||
|  |     u8 favorite_color{}; | ||||||
|  |     u8 gender{}; | ||||||
|  |     u8 height{}; | ||||||
|  |     u8 build{}; | ||||||
|  |     u8 type{}; | ||||||
|  |     u8 region_move{}; | ||||||
|  |     u8 faceline_type{}; | ||||||
|  |     u8 faceline_color{}; | ||||||
|  |     u8 faceline_wrinkle{}; | ||||||
|  |     u8 faceline_make{}; | ||||||
|  |     u8 hair_type{}; | ||||||
|  |     u8 hair_color{}; | ||||||
|  |     u8 hair_flip{}; | ||||||
|  |     u8 eye_type{}; | ||||||
|  |     u8 eye_color{}; | ||||||
|  |     u8 eye_scale{}; | ||||||
|  |     u8 eye_aspect{}; | ||||||
|  |     u8 eye_rotate{}; | ||||||
|  |     u8 eye_x{}; | ||||||
|  |     u8 eye_y{}; | ||||||
|  |     u8 eyebrow_type{}; | ||||||
|  |     u8 eyebrow_color{}; | ||||||
|  |     u8 eyebrow_scale{}; | ||||||
|  |     u8 eyebrow_aspect{}; | ||||||
|  |     u8 eyebrow_rotate{}; | ||||||
|  |     u8 eyebrow_x{}; | ||||||
|  |     u8 eyebrow_y{}; | ||||||
|  |     u8 nose_type{}; | ||||||
|  |     u8 nose_scale{}; | ||||||
|  |     u8 nose_y{}; | ||||||
|  |     u8 mouth_type{}; | ||||||
|  |     u8 mouth_color{}; | ||||||
|  |     u8 mouth_scale{}; | ||||||
|  |     u8 mouth_aspect{}; | ||||||
|  |     u8 mouth_y{}; | ||||||
|  |     u8 beard_color{}; | ||||||
|  |     u8 beard_type{}; | ||||||
|  |     u8 mustache_type{}; | ||||||
|  |     u8 mustache_scale{}; | ||||||
|  |     u8 mustache_y{}; | ||||||
|  |     u8 glasses_type{}; | ||||||
|  |     u8 glasses_color{}; | ||||||
|  |     u8 glasses_scale{}; | ||||||
|  |     u8 glasses_y{}; | ||||||
|  |     u8 mole_type{}; | ||||||
|  |     u8 mole_scale{}; | ||||||
|  |     u8 mole_x{}; | ||||||
|  |     u8 mole_y{}; | ||||||
|  |     INSERT_PADDING_BYTES(1); | ||||||
|  |  | ||||||
|  |     std::u16string Name() const; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size."); | ||||||
|  | static_assert(std::has_unique_object_representations_v<MiiInfo>, | ||||||
|  |               "All bits of MiiInfo must contribute to its value."); | ||||||
|  |  | ||||||
|  | #pragma pack(push, 4) | ||||||
|  |  | ||||||
|  | struct MiiInfoElement { | ||||||
|  |     MiiInfoElement(const MiiInfo& info, Source source) : info{info}, source{source} {} | ||||||
|  |  | ||||||
|  |     MiiInfo info{}; | ||||||
|  |     Source source{}; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); | ||||||
|  |  | ||||||
|  | struct MiiStoreBitFields { | ||||||
|  |     union { | ||||||
|  |         u32 word_0{}; | ||||||
|  |  | ||||||
|  |         BitField<0, 8, u32> hair_type; | ||||||
|  |         BitField<8, 7, u32> height; | ||||||
|  |         BitField<15, 1, u32> mole_type; | ||||||
|  |         BitField<16, 7, u32> build; | ||||||
|  |         BitField<23, 1, HairFlip> hair_flip; | ||||||
|  |         BitField<24, 7, u32> hair_color; | ||||||
|  |         BitField<31, 1, u32> type; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     union { | ||||||
|  |         u32 word_1{}; | ||||||
|  |  | ||||||
|  |         BitField<0, 7, u32> eye_color; | ||||||
|  |         BitField<7, 1, Gender> gender; | ||||||
|  |         BitField<8, 7, u32> eyebrow_color; | ||||||
|  |         BitField<16, 7, u32> mouth_color; | ||||||
|  |         BitField<24, 7, u32> beard_color; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     union { | ||||||
|  |         u32 word_2{}; | ||||||
|  |  | ||||||
|  |         BitField<0, 7, u32> glasses_color; | ||||||
|  |         BitField<8, 6, u32> eye_type; | ||||||
|  |         BitField<14, 2, u32> region_move; | ||||||
|  |         BitField<16, 6, u32> mouth_type; | ||||||
|  |         BitField<22, 2, FontRegion> font_region; | ||||||
|  |         BitField<24, 5, u32> eye_y; | ||||||
|  |         BitField<29, 3, u32> glasses_scale; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     union { | ||||||
|  |         u32 word_3{}; | ||||||
|  |  | ||||||
|  |         BitField<0, 5, u32> eyebrow_type; | ||||||
|  |         BitField<5, 3, MustacheType> mustache_type; | ||||||
|  |         BitField<8, 5, u32> nose_type; | ||||||
|  |         BitField<13, 3, BeardType> beard_type; | ||||||
|  |         BitField<16, 5, u32> nose_y; | ||||||
|  |         BitField<21, 3, u32> mouth_aspect; | ||||||
|  |         BitField<24, 5, u32> mouth_y; | ||||||
|  |         BitField<29, 3, u32> eyebrow_aspect; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     union { | ||||||
|  |         u32 word_4{}; | ||||||
|  |  | ||||||
|  |         BitField<0, 5, u32> mustache_y; | ||||||
|  |         BitField<5, 3, u32> eye_rotate; | ||||||
|  |         BitField<8, 5, u32> glasses_y; | ||||||
|  |         BitField<13, 3, u32> eye_aspect; | ||||||
|  |         BitField<16, 5, u32> mole_x; | ||||||
|  |         BitField<21, 3, u32> eye_scale; | ||||||
|  |         BitField<24, 5, u32> mole_y; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     union { | ||||||
|  |         u32 word_5{}; | ||||||
|  |  | ||||||
|  |         BitField<0, 5, u32> glasses_type; | ||||||
|  |         BitField<8, 4, u32> favorite_color; | ||||||
|  |         BitField<12, 4, u32> faceline_type; | ||||||
|  |         BitField<16, 4, u32> faceline_color; | ||||||
|  |         BitField<20, 4, u32> faceline_wrinkle; | ||||||
|  |         BitField<24, 4, u32> faceline_makeup; | ||||||
|  |         BitField<28, 4, u32> eye_x; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     union { | ||||||
|  |         u32 word_6{}; | ||||||
|  |  | ||||||
|  |         BitField<0, 4, u32> eyebrow_scale; | ||||||
|  |         BitField<4, 4, u32> eyebrow_rotate; | ||||||
|  |         BitField<8, 4, u32> eyebrow_x; | ||||||
|  |         BitField<12, 4, u32> eyebrow_y; | ||||||
|  |         BitField<16, 4, u32> nose_scale; | ||||||
|  |         BitField<20, 4, u32> mouth_scale; | ||||||
|  |         BitField<24, 4, u32> mustache_scale; | ||||||
|  |         BitField<28, 4, u32> mole_scale; | ||||||
|  |     }; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size."); | ||||||
|  | static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>, | ||||||
|  |               "MiiStoreBitFields is not trivially copyable."); | ||||||
|  |  | ||||||
|  | struct MiiStoreData { | ||||||
|  |     using Name = std::array<char16_t, 10>; | ||||||
|  |  | ||||||
|  |     MiiStoreData(); | ||||||
|  |     MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields, | ||||||
|  |                  const Common::UUID& user_id); | ||||||
|  |  | ||||||
|  |     // This corresponds to the above structure MiiStoreBitFields. I did it like this because the | ||||||
|  |     // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is | ||||||
|  |     // not suitable for our uses. | ||||||
|  |     struct { | ||||||
|  |         std::array<u8, 0x1C> data{}; | ||||||
|  |         static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size."); | ||||||
|  |  | ||||||
|  |         Name name{}; | ||||||
|  |         Common::UUID uuid{Common::INVALID_UUID}; | ||||||
|  |     } data; | ||||||
|  |  | ||||||
|  |     u16 data_crc{}; | ||||||
|  |     u16 device_crc{}; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size."); | ||||||
|  |  | ||||||
|  | struct MiiStoreDataElement { | ||||||
|  |     MiiStoreData data{}; | ||||||
|  |     Source source{}; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size."); | ||||||
|  |  | ||||||
|  | struct MiiDatabase { | ||||||
|  |     u32 magic{}; // 'NFDB' | ||||||
|  |     std::array<MiiStoreData, 0x64> miis{}; | ||||||
|  |     INSERT_PADDING_BYTES(1); | ||||||
|  |     u8 count{}; | ||||||
|  |     u16 crc{}; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size."); | ||||||
|  |  | ||||||
|  | struct RandomMiiValues { | ||||||
|  |     std::array<u8, 0xbc> values{}; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); | ||||||
|  |  | ||||||
|  | struct RandomMiiData4 { | ||||||
|  |     Gender gender{}; | ||||||
|  |     Age age{}; | ||||||
|  |     Race race{}; | ||||||
|  |     u32 values_count{}; | ||||||
|  |     std::array<u8, 0xbc> values{}; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); | ||||||
|  |  | ||||||
|  | struct RandomMiiData3 { | ||||||
|  |     u32 arg_1; | ||||||
|  |     u32 arg_2; | ||||||
|  |     u32 values_count; | ||||||
|  |     std::array<u8, 0xbc> values{}; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); | ||||||
|  |  | ||||||
|  | struct RandomMiiData2 { | ||||||
|  |     u32 arg_1; | ||||||
|  |     u32 values_count; | ||||||
|  |     std::array<u8, 0xbc> values{}; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); | ||||||
|  |  | ||||||
|  | struct DefaultMii { | ||||||
|  |     u32 face_type{}; | ||||||
|  |     u32 face_color{}; | ||||||
|  |     u32 face_wrinkle{}; | ||||||
|  |     u32 face_makeup{}; | ||||||
|  |     u32 hair_type{}; | ||||||
|  |     u32 hair_color{}; | ||||||
|  |     u32 hair_flip{}; | ||||||
|  |     u32 eye_type{}; | ||||||
|  |     u32 eye_color{}; | ||||||
|  |     u32 eye_scale{}; | ||||||
|  |     u32 eye_aspect{}; | ||||||
|  |     u32 eye_rotate{}; | ||||||
|  |     u32 eye_x{}; | ||||||
|  |     u32 eye_y{}; | ||||||
|  |     u32 eyebrow_type{}; | ||||||
|  |     u32 eyebrow_color{}; | ||||||
|  |     u32 eyebrow_scale{}; | ||||||
|  |     u32 eyebrow_aspect{}; | ||||||
|  |     u32 eyebrow_rotate{}; | ||||||
|  |     u32 eyebrow_x{}; | ||||||
|  |     u32 eyebrow_y{}; | ||||||
|  |     u32 nose_type{}; | ||||||
|  |     u32 nose_scale{}; | ||||||
|  |     u32 nose_y{}; | ||||||
|  |     u32 mouth_type{}; | ||||||
|  |     u32 mouth_color{}; | ||||||
|  |     u32 mouth_scale{}; | ||||||
|  |     u32 mouth_aspect{}; | ||||||
|  |     u32 mouth_y{}; | ||||||
|  |     u32 mustache_type{}; | ||||||
|  |     u32 beard_type{}; | ||||||
|  |     u32 beard_color{}; | ||||||
|  |     u32 mustache_scale{}; | ||||||
|  |     u32 mustache_y{}; | ||||||
|  |     u32 glasses_type{}; | ||||||
|  |     u32 glasses_color{}; | ||||||
|  |     u32 glasses_scale{}; | ||||||
|  |     u32 glasses_y{}; | ||||||
|  |     u32 mole_type{}; | ||||||
|  |     u32 mole_scale{}; | ||||||
|  |     u32 mole_x{}; | ||||||
|  |     u32 mole_y{}; | ||||||
|  |     u32 height{}; | ||||||
|  |     u32 weight{}; | ||||||
|  |     Gender gender{}; | ||||||
|  |     u32 favorite_color{}; | ||||||
|  |     u32 region{}; | ||||||
|  |     FontRegion font_region{}; | ||||||
|  |     u32 type{}; | ||||||
|  |     INSERT_PADDING_WORDS(5); | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size."); | ||||||
|  |  | ||||||
|  | #pragma pack(pop) | ||||||
|  |  | ||||||
|  | // The Mii manager is responsible for loading and storing the Miis to the database in NAND along | ||||||
|  | // with providing an easy interface for HLE emulation of the mii service. | ||||||
|  | class MiiManager { | ||||||
|  | public: | ||||||
|  |     MiiManager(); | ||||||
|  |  | ||||||
|  |     bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); | ||||||
|  |     bool IsFullDatabase() const; | ||||||
|  |     u32 GetCount(SourceFlag source_flag) const; | ||||||
|  |     ResultVal<MiiInfo> UpdateLatest(const MiiInfo& info, SourceFlag source_flag); | ||||||
|  |     MiiInfo BuildRandom(Age age, Gender gender, Race race); | ||||||
|  |     MiiInfo BuildDefault(std::size_t index); | ||||||
|  |     ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag); | ||||||
|  |     ResultCode GetIndex(const MiiInfo& info, u32& index); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     const Common::UUID user_id; | ||||||
|  |     u64 update_counter{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }; // namespace Service::Mii | ||||||
| @@ -4,22 +4,17 @@ | |||||||
|  |  | ||||||
| #include <memory> | #include <memory> | ||||||
|  |  | ||||||
| #include <fmt/ostream.h> |  | ||||||
|  |  | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/string_util.h" |  | ||||||
| #include "core/hle/ipc_helpers.h" | #include "core/hle/ipc_helpers.h" | ||||||
| #include "core/hle/kernel/hle_ipc.h" | #include "core/hle/kernel/hle_ipc.h" | ||||||
|  | #include "core/hle/service/mii/manager.h" | ||||||
| #include "core/hle/service/mii/mii.h" | #include "core/hle/service/mii/mii.h" | ||||||
| #include "core/hle/service/mii/mii_manager.h" |  | ||||||
| #include "core/hle/service/service.h" | #include "core/hle/service/service.h" | ||||||
| #include "core/hle/service/sm/sm.h" | #include "core/hle/service/sm/sm.h" | ||||||
|  |  | ||||||
| namespace Service::Mii { | namespace Service::Mii { | ||||||
|  |  | ||||||
| constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1}; | constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1}; | ||||||
| constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; |  | ||||||
| constexpr ResultCode ERROR_NOT_IN_TEST_MODE{ErrorModule::Mii, 99}; |  | ||||||
|  |  | ||||||
| class IDatabaseService final : public ServiceFramework<IDatabaseService> { | class IDatabaseService final : public ServiceFramework<IDatabaseService> { | ||||||
| public: | public: | ||||||
| @@ -31,19 +26,19 @@ public: | |||||||
|             {2, &IDatabaseService::GetCount, "GetCount"}, |             {2, &IDatabaseService::GetCount, "GetCount"}, | ||||||
|             {3, &IDatabaseService::Get, "Get"}, |             {3, &IDatabaseService::Get, "Get"}, | ||||||
|             {4, &IDatabaseService::Get1, "Get1"}, |             {4, &IDatabaseService::Get1, "Get1"}, | ||||||
|             {5, nullptr, "UpdateLatest"}, |             {5, &IDatabaseService::UpdateLatest, "UpdateLatest"}, | ||||||
|             {6, &IDatabaseService::BuildRandom, "BuildRandom"}, |             {6, &IDatabaseService::BuildRandom, "BuildRandom"}, | ||||||
|             {7, &IDatabaseService::BuildDefault, "BuildDefault"}, |             {7, &IDatabaseService::BuildDefault, "BuildDefault"}, | ||||||
|             {8, &IDatabaseService::Get2, "Get2"}, |             {8, nullptr, "Get2"}, | ||||||
|             {9, &IDatabaseService::Get3, "Get3"}, |             {9, nullptr, "Get3"}, | ||||||
|             {10, nullptr, "UpdateLatest1"}, |             {10, nullptr, "UpdateLatest1"}, | ||||||
|             {11, &IDatabaseService::FindIndex, "FindIndex"}, |             {11, nullptr, "FindIndex"}, | ||||||
|             {12, &IDatabaseService::Move, "Move"}, |             {12, nullptr, "Move"}, | ||||||
|             {13, &IDatabaseService::AddOrReplace, "AddOrReplace"}, |             {13, nullptr, "AddOrReplace"}, | ||||||
|             {14, &IDatabaseService::Delete, "Delete"}, |             {14, nullptr, "Delete"}, | ||||||
|             {15, &IDatabaseService::DestroyFile, "DestroyFile"}, |             {15, nullptr, "DestroyFile"}, | ||||||
|             {16, &IDatabaseService::DeleteFile, "DeleteFile"}, |             {16, nullptr, "DeleteFile"}, | ||||||
|             {17, &IDatabaseService::Format, "Format"}, |             {17, nullptr, "Format"}, | ||||||
|             {18, nullptr, "Import"}, |             {18, nullptr, "Import"}, | ||||||
|             {19, nullptr, "Export"}, |             {19, nullptr, "Export"}, | ||||||
|             {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, |             {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, | ||||||
| @@ -59,31 +54,26 @@ public: | |||||||
|     } |     } | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     template <typename OutType> |     template <typename T> | ||||||
|     std::vector<u8> SerializeArray(OutType (MiiManager::*getter)(u32) const, u32 offset, |     std::vector<u8> SerializeArray(const std::vector<T>& values) { | ||||||
|                                    u32 requested_size, u32& read_size) { |         std::vector<u8> out(values.size() * sizeof(T)); | ||||||
|         read_size = std::min(requested_size, db.Size() - offset); |         std::size_t offset{}; | ||||||
|  |         for (const auto& value : values) { | ||||||
|         std::vector<u8> out(read_size * sizeof(OutType)); |             std::memcpy(out.data() + offset, &value, sizeof(T)); | ||||||
|  |             offset += sizeof(T); | ||||||
|         for (u32 i = 0; i < read_size; ++i) { |  | ||||||
|             const auto obj = (db.*getter)(offset + i); |  | ||||||
|             std::memcpy(out.data() + i * sizeof(OutType), &obj, sizeof(OutType)); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return out; |         return out; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void IsUpdated(Kernel::HLERequestContext& ctx) { |     void IsUpdated(Kernel::HLERequestContext& ctx) { | ||||||
|         IPC::RequestParser rp{ctx}; |         IPC::RequestParser rp{ctx}; | ||||||
|         const auto source{rp.PopRaw<Source>()}; |         const auto source_flag{rp.PopRaw<SourceFlag>()}; | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with source={}", source); |         LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 3}; |         IPC::ResponseBuilder rb{ctx, 3}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|         rb.Push(db.CheckUpdatedFlag()); |         rb.Push(manager.CheckAndResetUpdateCounter(source_flag, current_update_counter)); | ||||||
|         db.ResetUpdatedFlag(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void IsFullDatabase(Kernel::HLERequestContext& ctx) { |     void IsFullDatabase(Kernel::HLERequestContext& ctx) { | ||||||
| @@ -91,93 +81,126 @@ private: | |||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 3}; |         IPC::ResponseBuilder rb{ctx, 3}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|         rb.Push(db.Full()); |         rb.Push(manager.IsFullDatabase()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void GetCount(Kernel::HLERequestContext& ctx) { |     void GetCount(Kernel::HLERequestContext& ctx) { | ||||||
|         IPC::RequestParser rp{ctx}; |         IPC::RequestParser rp{ctx}; | ||||||
|         const auto source{rp.PopRaw<Source>()}; |         const auto source_flag{rp.PopRaw<SourceFlag>()}; | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with source={}", source); |         LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 3}; |         IPC::ResponseBuilder rb{ctx, 3}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|         rb.Push<u32>(db.Size()); |         rb.Push<u32>(manager.GetCount(source_flag)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Gets Miis from database at offset and index in format MiiInfoElement |  | ||||||
|     void Get(Kernel::HLERequestContext& ctx) { |     void Get(Kernel::HLERequestContext& ctx) { | ||||||
|         IPC::RequestParser rp{ctx}; |         IPC::RequestParser rp{ctx}; | ||||||
|         const auto size{rp.PopRaw<u32>()}; |         const auto source_flag{rp.PopRaw<SourceFlag>()}; | ||||||
|         const auto source{rp.PopRaw<Source>()}; |  | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size, |         LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | ||||||
|                   offsets[0], source); |  | ||||||
|  |  | ||||||
|         u32 read_size{}; |         const auto result{manager.GetDefault(source_flag)}; | ||||||
|         ctx.WriteBuffer(SerializeArray(&MiiManager::GetInfoElement, offsets[0], size, read_size)); |         if (result.Failed()) { | ||||||
|         offsets[0] += read_size; |             IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|  |             rb.Push(result.Code()); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (result->size() > 0) { | ||||||
|  |             ctx.WriteBuffer(SerializeArray(*result)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 3}; |         IPC::ResponseBuilder rb{ctx, 3}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|         rb.Push<u32>(read_size); |         rb.Push<u32>(static_cast<u32>(result->size())); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Gets Miis from database at offset and index in format MiiInfo |  | ||||||
|     void Get1(Kernel::HLERequestContext& ctx) { |     void Get1(Kernel::HLERequestContext& ctx) { | ||||||
|         IPC::RequestParser rp{ctx}; |         IPC::RequestParser rp{ctx}; | ||||||
|         const auto size{rp.PopRaw<u32>()}; |         const auto source_flag{rp.PopRaw<SourceFlag>()}; | ||||||
|         const auto source{rp.PopRaw<Source>()}; |  | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size, |         LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | ||||||
|                   offsets[1], source); |  | ||||||
|  |  | ||||||
|         u32 read_size{}; |         const auto result{manager.GetDefault(source_flag)}; | ||||||
|         ctx.WriteBuffer(SerializeArray(&MiiManager::GetInfo, offsets[1], size, read_size)); |         if (result.Failed()) { | ||||||
|         offsets[1] += read_size; |             IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|  |             rb.Push(result.Code()); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         std::vector<MiiInfo> values; | ||||||
|  |         for (const auto& element : *result) { | ||||||
|  |             values.emplace_back(element.info); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ctx.WriteBuffer(SerializeArray(values)); | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 3}; |         IPC::ResponseBuilder rb{ctx, 3}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|         rb.Push<u32>(read_size); |         rb.Push<u32>(static_cast<u32>(result->size())); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void UpdateLatest(Kernel::HLERequestContext& ctx) { | ||||||
|  |         IPC::RequestParser rp{ctx}; | ||||||
|  |         const auto info{rp.PopRaw<MiiInfo>()}; | ||||||
|  |         const auto source_flag{rp.PopRaw<SourceFlag>()}; | ||||||
|  |  | ||||||
|  |         LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); | ||||||
|  |  | ||||||
|  |         const auto result{manager.UpdateLatest(info, source_flag)}; | ||||||
|  |         if (result.Failed()) { | ||||||
|  |             IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|  |             rb.Push(result.Code()); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; | ||||||
|  |         rb.Push(RESULT_SUCCESS); | ||||||
|  |         rb.PushRaw<MiiInfo>(*result); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void BuildRandom(Kernel::HLERequestContext& ctx) { |     void BuildRandom(Kernel::HLERequestContext& ctx) { | ||||||
|         IPC::RequestParser rp{ctx}; |         IPC::RequestParser rp{ctx}; | ||||||
|         const auto [unknown1, unknown2, unknown3] = rp.PopRaw<RandomParameters>(); |  | ||||||
|  |  | ||||||
|         if (unknown1 > 3) { |         const auto age{rp.PopRaw<Age>()}; | ||||||
|  |         const auto gender{rp.PopRaw<Gender>()}; | ||||||
|  |         const auto race{rp.PopRaw<Race>()}; | ||||||
|  |  | ||||||
|  |         LOG_DEBUG(Service_Mii, "called with age={}, gender={}, race={}", age, gender, race); | ||||||
|  |  | ||||||
|  |         if (age > Age::All) { | ||||||
|             IPC::ResponseBuilder rb{ctx, 2}; |             IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|             rb.Push(ERROR_INVALID_ARGUMENT); |             rb.Push(ERROR_INVALID_ARGUMENT); | ||||||
|             LOG_ERROR(Service_Mii, "Invalid unknown1 value: {}", unknown1); |             LOG_ERROR(Service_Mii, "invalid age={}", age); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (unknown2 > 2) { |         if (gender > Gender::All) { | ||||||
|             IPC::ResponseBuilder rb{ctx, 2}; |             IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|             rb.Push(ERROR_INVALID_ARGUMENT); |             rb.Push(ERROR_INVALID_ARGUMENT); | ||||||
|             LOG_ERROR(Service_Mii, "Invalid unknown2 value: {}", unknown2); |             LOG_ERROR(Service_Mii, "invalid gender={}", gender); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (unknown3 > 3) { |         if (race > Race::All) { | ||||||
|             IPC::ResponseBuilder rb{ctx, 2}; |             IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|             rb.Push(ERROR_INVALID_ARGUMENT); |             rb.Push(ERROR_INVALID_ARGUMENT); | ||||||
|             LOG_ERROR(Service_Mii, "Invalid unknown3 value: {}", unknown3); |             LOG_ERROR(Service_Mii, "invalid race={}", race); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with param_1={:08X}, param_2={:08X}, param_3={:08X}", |  | ||||||
|                   unknown1, unknown2, unknown3); |  | ||||||
|  |  | ||||||
|         const auto info = db.CreateRandom({unknown1, unknown2, unknown3}); |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; |         IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|         rb.PushRaw<MiiInfo>(info); |         rb.PushRaw<MiiInfo>(manager.BuildRandom(age, gender, race)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void BuildDefault(Kernel::HLERequestContext& ctx) { |     void BuildDefault(Kernel::HLERequestContext& ctx) { | ||||||
|         IPC::RequestParser rp{ctx}; |         IPC::RequestParser rp{ctx}; | ||||||
|         const auto index{rp.PopRaw<u32>()}; |         const auto index{rp.Pop<u32>()}; | ||||||
|  |  | ||||||
|  |         LOG_DEBUG(Service_Mii, "called with index={}", index); | ||||||
|  |  | ||||||
|         if (index > 5) { |         if (index > 5) { | ||||||
|             LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}", |             LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}", | ||||||
| @@ -187,168 +210,20 @@ private: | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with index={:08X}", index); |  | ||||||
|  |  | ||||||
|         const auto info = db.CreateDefault(index); |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; |         IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; | ||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|         rb.PushRaw<MiiInfo>(info); |         rb.PushRaw<MiiInfo>(manager.BuildDefault(index)); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Gets Miis from database at offset and index in format MiiStoreDataElement |  | ||||||
|     void Get2(Kernel::HLERequestContext& ctx) { |  | ||||||
|         IPC::RequestParser rp{ctx}; |  | ||||||
|         const auto size{rp.PopRaw<u32>()}; |  | ||||||
|         const auto source{rp.PopRaw<Source>()}; |  | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size, |  | ||||||
|                   offsets[2], source); |  | ||||||
|  |  | ||||||
|         u32 read_size{}; |  | ||||||
|         ctx.WriteBuffer( |  | ||||||
|             SerializeArray(&MiiManager::GetStoreDataElement, offsets[2], size, read_size)); |  | ||||||
|         offsets[2] += read_size; |  | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 3}; |  | ||||||
|         rb.Push(RESULT_SUCCESS); |  | ||||||
|         rb.Push<u32>(read_size); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Gets Miis from database at offset and index in format MiiStoreData |  | ||||||
|     void Get3(Kernel::HLERequestContext& ctx) { |  | ||||||
|         IPC::RequestParser rp{ctx}; |  | ||||||
|         const auto size{rp.PopRaw<u32>()}; |  | ||||||
|         const auto source{rp.PopRaw<Source>()}; |  | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with size={:08X}, offset={:08X}, source={}", size, |  | ||||||
|                   offsets[3], source); |  | ||||||
|  |  | ||||||
|         u32 read_size{}; |  | ||||||
|         ctx.WriteBuffer(SerializeArray(&MiiManager::GetStoreData, offsets[3], size, read_size)); |  | ||||||
|         offsets[3] += read_size; |  | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 3}; |  | ||||||
|         rb.Push(RESULT_SUCCESS); |  | ||||||
|         rb.Push<u32>(read_size); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void FindIndex(Kernel::HLERequestContext& ctx) { |  | ||||||
|         IPC::RequestParser rp{ctx}; |  | ||||||
|         const auto uuid{rp.PopRaw<Common::UUID>()}; |  | ||||||
|         const auto unknown{rp.PopRaw<bool>()}; |  | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with uuid={}, unknown={}", uuid.FormatSwitch(), unknown); |  | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 3}; |  | ||||||
|  |  | ||||||
|         const auto index = db.IndexOf(uuid); |  | ||||||
|         if (index > MAX_MIIS) { |  | ||||||
|             // TODO(DarkLordZach): Find a better error code |  | ||||||
|             rb.Push(RESULT_UNKNOWN); |  | ||||||
|             rb.Push(index); |  | ||||||
|         } else { |  | ||||||
|             rb.Push(RESULT_SUCCESS); |  | ||||||
|             rb.Push(index); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void Move(Kernel::HLERequestContext& ctx) { |  | ||||||
|         IPC::RequestParser rp{ctx}; |  | ||||||
|         const auto uuid{rp.PopRaw<Common::UUID>()}; |  | ||||||
|         const auto index{rp.PopRaw<s32>()}; |  | ||||||
|  |  | ||||||
|         if (index < 0) { |  | ||||||
|             LOG_ERROR(Service_Mii, "Index cannot be negative but is {:08X}!", index); |  | ||||||
|             IPC::ResponseBuilder rb{ctx, 2}; |  | ||||||
|             rb.Push(ERROR_INVALID_ARGUMENT); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with uuid={}, index={:08X}", uuid.FormatSwitch(), index); |  | ||||||
|  |  | ||||||
|         const auto success = db.Move(uuid, index); |  | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |  | ||||||
|         // TODO(DarkLordZach): Find a better error code |  | ||||||
|         rb.Push(success ? RESULT_SUCCESS : RESULT_UNKNOWN); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void AddOrReplace(Kernel::HLERequestContext& ctx) { |  | ||||||
|         IPC::RequestParser rp{ctx}; |  | ||||||
|         const auto data{rp.PopRaw<MiiStoreData>()}; |  | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with Mii data uuid={}, name={}", data.uuid.FormatSwitch(), |  | ||||||
|                   Common::UTF16ToUTF8(data.Name())); |  | ||||||
|  |  | ||||||
|         const auto success = db.AddOrReplace(data); |  | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |  | ||||||
|         // TODO(DarkLordZach): Find a better error code |  | ||||||
|         rb.Push(success ? RESULT_SUCCESS : RESULT_UNKNOWN); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void Delete(Kernel::HLERequestContext& ctx) { |  | ||||||
|         IPC::RequestParser rp{ctx}; |  | ||||||
|         const auto uuid{rp.PopRaw<Common::UUID>()}; |  | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with uuid={}", uuid.FormatSwitch()); |  | ||||||
|  |  | ||||||
|         const auto success = db.Remove(uuid); |  | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |  | ||||||
|         rb.Push(success ? RESULT_SUCCESS : ERROR_CANNOT_FIND_ENTRY); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void DestroyFile(Kernel::HLERequestContext& ctx) { |  | ||||||
|         LOG_DEBUG(Service_Mii, "called"); |  | ||||||
|  |  | ||||||
|         if (!db.IsTestModeEnabled()) { |  | ||||||
|             LOG_ERROR(Service_Mii, "Database is not in test mode -- cannot destory database file."); |  | ||||||
|             IPC::ResponseBuilder rb{ctx, 2}; |  | ||||||
|             rb.Push(ERROR_NOT_IN_TEST_MODE); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 3}; |  | ||||||
|         rb.Push(RESULT_SUCCESS); |  | ||||||
|         rb.Push(db.DestroyFile()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void DeleteFile(Kernel::HLERequestContext& ctx) { |  | ||||||
|         LOG_DEBUG(Service_Mii, "called"); |  | ||||||
|  |  | ||||||
|         if (!db.IsTestModeEnabled()) { |  | ||||||
|             LOG_ERROR(Service_Mii, "Database is not in test mode -- cannot delete database file."); |  | ||||||
|             IPC::ResponseBuilder rb{ctx, 2}; |  | ||||||
|             rb.Push(ERROR_NOT_IN_TEST_MODE); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 3}; |  | ||||||
|         rb.Push(RESULT_SUCCESS); |  | ||||||
|         rb.Push(db.DeleteFile()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void Format(Kernel::HLERequestContext& ctx) { |  | ||||||
|         LOG_DEBUG(Service_Mii, "called"); |  | ||||||
|  |  | ||||||
|         db.Clear(); |  | ||||||
|  |  | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |  | ||||||
|         rb.Push(RESULT_SUCCESS); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void GetIndex(Kernel::HLERequestContext& ctx) { |     void GetIndex(Kernel::HLERequestContext& ctx) { | ||||||
|         IPC::RequestParser rp{ctx}; |         IPC::RequestParser rp{ctx}; | ||||||
|         const auto info{rp.PopRaw<MiiInfo>()}; |         const auto info{rp.PopRaw<MiiInfo>()}; | ||||||
|  |  | ||||||
|         LOG_DEBUG(Service_Mii, "called with Mii info uuid={}, name={}", info.uuid.FormatSwitch(), |         LOG_DEBUG(Service_Mii, "called"); | ||||||
|                   Common::UTF16ToUTF8(info.Name())); |  | ||||||
|  |  | ||||||
|         const auto index = db.IndexOf(info); |         u32 index{}; | ||||||
|  |         IPC::ResponseBuilder rb{ctx, 3}; | ||||||
|         IPC::ResponseBuilder rb{ctx, 2}; |         rb.Push(manager.GetIndex(info, index)); | ||||||
|         rb.Push(RESULT_SUCCESS); |  | ||||||
|         rb.Push(index); |         rb.Push(index); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -364,12 +239,14 @@ private: | |||||||
|         rb.Push(RESULT_SUCCESS); |         rb.Push(RESULT_SUCCESS); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     MiiManager db; |     constexpr bool IsInterfaceVersionSupported(u32 interface_version) const { | ||||||
|  |         return current_interface_version >= interface_version; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     u32 current_interface_version = 0; |     MiiManager manager; | ||||||
|  |  | ||||||
|     // Last read offsets of Get functions |     u32 current_interface_version{}; | ||||||
|     std::array<u32, 4> offsets{}; |     u64 current_update_counter{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class MiiDBModule final : public ServiceFramework<MiiDBModule> { | class MiiDBModule final : public ServiceFramework<MiiDBModule> { | ||||||
|   | |||||||
| @@ -1,420 +0,0 @@ | |||||||
| // Copyright 2018 yuzu emulator team |  | ||||||
| // Licensed under GPLv2 or any later version |  | ||||||
| // Refer to the license.txt file included. |  | ||||||
|  |  | ||||||
| #include <algorithm> |  | ||||||
| #include <cstring> |  | ||||||
| #include "common/assert.h" |  | ||||||
| #include "common/file_util.h" |  | ||||||
| #include "common/logging/log.h" |  | ||||||
| #include "common/string_util.h" |  | ||||||
| #include "core/hle/service/mii/mii_manager.h" |  | ||||||
|  |  | ||||||
| namespace Service::Mii { |  | ||||||
|  |  | ||||||
| namespace { |  | ||||||
|  |  | ||||||
| constexpr char MII_SAVE_DATABASE_PATH[] = "/system/save/8000000000000030/MiiDatabase.dat"; |  | ||||||
| constexpr std::array<char16_t, 11> DEFAULT_MII_NAME = {u'y', u'u', u'z', u'u', u'\0'}; |  | ||||||
|  |  | ||||||
| // This value was retrieved from HW test |  | ||||||
| constexpr MiiStoreData DEFAULT_MII = { |  | ||||||
|     { |  | ||||||
|         0x21, 0x40, 0x40, 0x01, 0x08, 0x01, 0x13, 0x08, 0x08, 0x02, 0x17, 0x8C, 0x06, 0x01, |  | ||||||
|         0x69, 0x6D, 0x8A, 0x6A, 0x82, 0x14, 0x00, 0x00, 0x00, 0x20, 0x64, 0x72, 0x44, 0x44, |  | ||||||
|     }, |  | ||||||
|     {'y', 'u', 'z', 'u', '\0'}, |  | ||||||
|     Common::UUID{1, 0}, |  | ||||||
|     0, |  | ||||||
|     0, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| // Default values taken from multiple real databases |  | ||||||
| const MiiDatabase DEFAULT_MII_DATABASE{Common::MakeMagic('N', 'F', 'D', 'B'), {}, {1}, 0, 0}; |  | ||||||
|  |  | ||||||
| constexpr std::array<const char*, 4> SOURCE_NAMES{ |  | ||||||
|     "Database", |  | ||||||
|     "Default", |  | ||||||
|     "Account", |  | ||||||
|     "Friend", |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize> |  | ||||||
| std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) { |  | ||||||
|     std::array<T, DestArraySize> out{}; |  | ||||||
|     std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize)); |  | ||||||
|     return out; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) { |  | ||||||
|     MiiStoreBitFields bf{}; |  | ||||||
|     std::memcpy(&bf, data.data.data(), sizeof(MiiStoreBitFields)); |  | ||||||
|     return { |  | ||||||
|         data.uuid, |  | ||||||
|         ResizeArray<char16_t, 10, 11>(data.name), |  | ||||||
|         static_cast<u8>(bf.font_region.Value()), |  | ||||||
|         static_cast<u8>(bf.favorite_color.Value()), |  | ||||||
|         static_cast<u8>(bf.gender.Value()), |  | ||||||
|         static_cast<u8>(bf.height.Value()), |  | ||||||
|         static_cast<u8>(bf.weight.Value()), |  | ||||||
|         static_cast<u8>(bf.mii_type.Value()), |  | ||||||
|         static_cast<u8>(bf.mii_region.Value()), |  | ||||||
|         static_cast<u8>(bf.face_type.Value()), |  | ||||||
|         static_cast<u8>(bf.face_color.Value()), |  | ||||||
|         static_cast<u8>(bf.face_wrinkle.Value()), |  | ||||||
|         static_cast<u8>(bf.face_makeup.Value()), |  | ||||||
|         static_cast<u8>(bf.hair_type.Value()), |  | ||||||
|         static_cast<u8>(bf.hair_color.Value()), |  | ||||||
|         static_cast<bool>(bf.hair_flip.Value()), |  | ||||||
|         static_cast<u8>(bf.eye_type.Value()), |  | ||||||
|         static_cast<u8>(bf.eye_color.Value()), |  | ||||||
|         static_cast<u8>(bf.eye_scale.Value()), |  | ||||||
|         static_cast<u8>(bf.eye_aspect.Value()), |  | ||||||
|         static_cast<u8>(bf.eye_rotate.Value()), |  | ||||||
|         static_cast<u8>(bf.eye_x.Value()), |  | ||||||
|         static_cast<u8>(bf.eye_y.Value()), |  | ||||||
|         static_cast<u8>(bf.eyebrow_type.Value()), |  | ||||||
|         static_cast<u8>(bf.eyebrow_color.Value()), |  | ||||||
|         static_cast<u8>(bf.eyebrow_scale.Value()), |  | ||||||
|         static_cast<u8>(bf.eyebrow_aspect.Value()), |  | ||||||
|         static_cast<u8>(bf.eyebrow_rotate.Value()), |  | ||||||
|         static_cast<u8>(bf.eyebrow_x.Value()), |  | ||||||
|         static_cast<u8>(bf.eyebrow_y.Value()), |  | ||||||
|         static_cast<u8>(bf.nose_type.Value()), |  | ||||||
|         static_cast<u8>(bf.nose_scale.Value()), |  | ||||||
|         static_cast<u8>(bf.nose_y.Value()), |  | ||||||
|         static_cast<u8>(bf.mouth_type.Value()), |  | ||||||
|         static_cast<u8>(bf.mouth_color.Value()), |  | ||||||
|         static_cast<u8>(bf.mouth_scale.Value()), |  | ||||||
|         static_cast<u8>(bf.mouth_aspect.Value()), |  | ||||||
|         static_cast<u8>(bf.mouth_y.Value()), |  | ||||||
|         static_cast<u8>(bf.facial_hair_color.Value()), |  | ||||||
|         static_cast<u8>(bf.beard_type.Value()), |  | ||||||
|         static_cast<u8>(bf.mustache_type.Value()), |  | ||||||
|         static_cast<u8>(bf.mustache_scale.Value()), |  | ||||||
|         static_cast<u8>(bf.mustache_y.Value()), |  | ||||||
|         static_cast<u8>(bf.glasses_type.Value()), |  | ||||||
|         static_cast<u8>(bf.glasses_color.Value()), |  | ||||||
|         static_cast<u8>(bf.glasses_scale.Value()), |  | ||||||
|         static_cast<u8>(bf.glasses_y.Value()), |  | ||||||
|         static_cast<u8>(bf.mole_type.Value()), |  | ||||||
|         static_cast<u8>(bf.mole_scale.Value()), |  | ||||||
|         static_cast<u8>(bf.mole_x.Value()), |  | ||||||
|         static_cast<u8>(bf.mole_y.Value()), |  | ||||||
|         0x00, |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| MiiStoreData ConvertInfoToStoreData(const MiiInfo& info) { |  | ||||||
|     MiiStoreData out{}; |  | ||||||
|     out.name = ResizeArray<char16_t, 11, 10>(info.name); |  | ||||||
|     out.uuid = info.uuid; |  | ||||||
|  |  | ||||||
|     MiiStoreBitFields bf{}; |  | ||||||
|  |  | ||||||
|     bf.hair_type.Assign(info.hair_type); |  | ||||||
|     bf.mole_type.Assign(info.mole_type); |  | ||||||
|     bf.height.Assign(info.height); |  | ||||||
|     bf.hair_flip.Assign(info.hair_flip); |  | ||||||
|     bf.weight.Assign(info.weight); |  | ||||||
|     bf.hair_color.Assign(info.hair_color); |  | ||||||
|  |  | ||||||
|     bf.gender.Assign(info.gender); |  | ||||||
|     bf.eye_color.Assign(info.eye_color); |  | ||||||
|     bf.eyebrow_color.Assign(info.eyebrow_color); |  | ||||||
|     bf.mouth_color.Assign(info.mouth_color); |  | ||||||
|     bf.facial_hair_color.Assign(info.facial_hair_color); |  | ||||||
|  |  | ||||||
|     bf.mii_type.Assign(info.mii_type); |  | ||||||
|     bf.glasses_color.Assign(info.glasses_color); |  | ||||||
|     bf.font_region.Assign(info.font_region); |  | ||||||
|     bf.eye_type.Assign(info.eye_type); |  | ||||||
|     bf.mii_region.Assign(info.mii_region); |  | ||||||
|     bf.mouth_type.Assign(info.mouth_type); |  | ||||||
|     bf.glasses_scale.Assign(info.glasses_scale); |  | ||||||
|     bf.eye_y.Assign(info.eye_y); |  | ||||||
|  |  | ||||||
|     bf.mustache_type.Assign(info.mustache_type); |  | ||||||
|     bf.eyebrow_type.Assign(info.eyebrow_type); |  | ||||||
|     bf.beard_type.Assign(info.beard_type); |  | ||||||
|     bf.nose_type.Assign(info.nose_type); |  | ||||||
|     bf.mouth_aspect.Assign(info.mouth_aspect_ratio); |  | ||||||
|     bf.nose_y.Assign(info.nose_y); |  | ||||||
|     bf.eyebrow_aspect.Assign(info.eyebrow_aspect_ratio); |  | ||||||
|     bf.mouth_y.Assign(info.mouth_y); |  | ||||||
|  |  | ||||||
|     bf.eye_rotate.Assign(info.eye_rotate); |  | ||||||
|     bf.mustache_y.Assign(info.mustache_y); |  | ||||||
|     bf.eye_aspect.Assign(info.eye_aspect_ratio); |  | ||||||
|     bf.glasses_y.Assign(info.glasses_y); |  | ||||||
|     bf.eye_scale.Assign(info.eye_scale); |  | ||||||
|     bf.mole_x.Assign(info.mole_x); |  | ||||||
|     bf.mole_y.Assign(info.mole_y); |  | ||||||
|  |  | ||||||
|     bf.glasses_type.Assign(info.glasses_type); |  | ||||||
|     bf.face_type.Assign(info.face_type); |  | ||||||
|     bf.favorite_color.Assign(info.favorite_color); |  | ||||||
|     bf.face_wrinkle.Assign(info.face_wrinkle); |  | ||||||
|     bf.face_color.Assign(info.face_color); |  | ||||||
|     bf.eye_x.Assign(info.eye_x); |  | ||||||
|     bf.face_makeup.Assign(info.face_makeup); |  | ||||||
|  |  | ||||||
|     bf.eyebrow_rotate.Assign(info.eyebrow_rotate); |  | ||||||
|     bf.eyebrow_scale.Assign(info.eyebrow_scale); |  | ||||||
|     bf.eyebrow_y.Assign(info.eyebrow_y); |  | ||||||
|     bf.eyebrow_x.Assign(info.eyebrow_x); |  | ||||||
|     bf.mouth_scale.Assign(info.mouth_scale); |  | ||||||
|     bf.nose_scale.Assign(info.nose_scale); |  | ||||||
|     bf.mole_scale.Assign(info.mole_scale); |  | ||||||
|     bf.mustache_scale.Assign(info.mustache_scale); |  | ||||||
|  |  | ||||||
|     std::memcpy(out.data.data(), &bf, sizeof(MiiStoreBitFields)); |  | ||||||
|  |  | ||||||
|     return out; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| } // namespace |  | ||||||
|  |  | ||||||
| std::ostream& operator<<(std::ostream& os, Source source) { |  | ||||||
|     if (static_cast<std::size_t>(source) >= SOURCE_NAMES.size()) { |  | ||||||
|         return os << "[UNKNOWN SOURCE]"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     os << SOURCE_NAMES.at(static_cast<std::size_t>(source)); |  | ||||||
|     return os; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| std::u16string MiiInfo::Name() const { |  | ||||||
|     return Common::UTF16StringFromFixedZeroTerminatedBuffer(name.data(), name.size()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool operator==(const MiiInfo& lhs, const MiiInfo& rhs) { |  | ||||||
|     return std::memcmp(&lhs, &rhs, sizeof(MiiInfo)) == 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool operator!=(const MiiInfo& lhs, const MiiInfo& rhs) { |  | ||||||
|     return !operator==(lhs, rhs); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| std::u16string MiiStoreData::Name() const { |  | ||||||
|     return Common::UTF16StringFromFixedZeroTerminatedBuffer(name.data(), name.size()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| MiiManager::MiiManager() = default; |  | ||||||
|  |  | ||||||
| MiiManager::~MiiManager() = default; |  | ||||||
|  |  | ||||||
| MiiInfo MiiManager::CreateRandom(RandomParameters params) { |  | ||||||
|     LOG_WARNING(Service_Mii, |  | ||||||
|                 "(STUBBED) called with params={:08X}{:08X}{:08X}, returning default Mii", |  | ||||||
|                 params.unknown_1, params.unknown_2, params.unknown_3); |  | ||||||
|  |  | ||||||
|     return ConvertStoreDataToInfo(CreateMiiWithUniqueUUID()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| MiiInfo MiiManager::CreateDefault(u32 index) { |  | ||||||
|     const auto new_mii = CreateMiiWithUniqueUUID(); |  | ||||||
|  |  | ||||||
|     database.miis.at(index) = new_mii; |  | ||||||
|  |  | ||||||
|     EnsureDatabasePartition(); |  | ||||||
|     return ConvertStoreDataToInfo(new_mii); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MiiManager::CheckUpdatedFlag() const { |  | ||||||
|     return updated_flag; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MiiManager::ResetUpdatedFlag() { |  | ||||||
|     updated_flag = false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MiiManager::IsTestModeEnabled() const { |  | ||||||
|     return is_test_mode_enabled; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MiiManager::Empty() const { |  | ||||||
|     return Size() == 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MiiManager::Full() const { |  | ||||||
|     return Size() == MAX_MIIS; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MiiManager::Clear() { |  | ||||||
|     updated_flag = true; |  | ||||||
|     std::fill(database.miis.begin(), database.miis.end(), MiiStoreData{}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| u32 MiiManager::Size() const { |  | ||||||
|     return static_cast<u32>(std::count_if(database.miis.begin(), database.miis.end(), |  | ||||||
|                                           [](const MiiStoreData& elem) { return elem.uuid; })); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| MiiInfo MiiManager::GetInfo(u32 index) const { |  | ||||||
|     return ConvertStoreDataToInfo(GetStoreData(index)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| MiiInfoElement MiiManager::GetInfoElement(u32 index) const { |  | ||||||
|     return {GetInfo(index), Source::Database}; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| MiiStoreData MiiManager::GetStoreData(u32 index) const { |  | ||||||
|     return database.miis.at(index); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| MiiStoreDataElement MiiManager::GetStoreDataElement(u32 index) const { |  | ||||||
|     return {GetStoreData(index), Source::Database}; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MiiManager::Remove(Common::UUID uuid) { |  | ||||||
|     const auto iter = std::find_if(database.miis.begin(), database.miis.end(), |  | ||||||
|                                    [uuid](const MiiStoreData& elem) { return elem.uuid == uuid; }); |  | ||||||
|  |  | ||||||
|     if (iter == database.miis.end()) |  | ||||||
|         return false; |  | ||||||
|  |  | ||||||
|     updated_flag = true; |  | ||||||
|     *iter = MiiStoreData{}; |  | ||||||
|     EnsureDatabasePartition(); |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| u32 MiiManager::IndexOf(Common::UUID uuid) const { |  | ||||||
|     const auto iter = std::find_if(database.miis.begin(), database.miis.end(), |  | ||||||
|                                    [uuid](const MiiStoreData& elem) { return elem.uuid == uuid; }); |  | ||||||
|  |  | ||||||
|     if (iter == database.miis.end()) |  | ||||||
|         return INVALID_INDEX; |  | ||||||
|  |  | ||||||
|     return static_cast<u32>(std::distance(database.miis.begin(), iter)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| u32 MiiManager::IndexOf(const MiiInfo& info) const { |  | ||||||
|     const auto iter = |  | ||||||
|         std::find_if(database.miis.begin(), database.miis.end(), [&info](const MiiStoreData& elem) { |  | ||||||
|             return ConvertStoreDataToInfo(elem) == info; |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|     if (iter == database.miis.end()) |  | ||||||
|         return INVALID_INDEX; |  | ||||||
|  |  | ||||||
|     return static_cast<u32>(std::distance(database.miis.begin(), iter)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MiiManager::Move(Common::UUID uuid, u32 new_index) { |  | ||||||
|     const auto index = IndexOf(uuid); |  | ||||||
|  |  | ||||||
|     if (index == INVALID_INDEX || new_index >= MAX_MIIS) |  | ||||||
|         return false; |  | ||||||
|  |  | ||||||
|     updated_flag = true; |  | ||||||
|     const auto moving = database.miis[index]; |  | ||||||
|     const auto replacing = database.miis[new_index]; |  | ||||||
|     if (replacing.uuid) { |  | ||||||
|         database.miis[index] = replacing; |  | ||||||
|         database.miis[new_index] = moving; |  | ||||||
|     } else { |  | ||||||
|         database.miis[index] = MiiStoreData{}; |  | ||||||
|         database.miis[new_index] = moving; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     EnsureDatabasePartition(); |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MiiManager::AddOrReplace(const MiiStoreData& data) { |  | ||||||
|     const auto index = IndexOf(data.uuid); |  | ||||||
|  |  | ||||||
|     updated_flag = true; |  | ||||||
|     if (index == INVALID_INDEX) { |  | ||||||
|         const auto size = Size(); |  | ||||||
|         if (size == MAX_MIIS) |  | ||||||
|             return false; |  | ||||||
|         database.miis[size] = data; |  | ||||||
|     } else { |  | ||||||
|         database.miis[index] = data; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MiiManager::DestroyFile() { |  | ||||||
|     database = DEFAULT_MII_DATABASE; |  | ||||||
|     updated_flag = false; |  | ||||||
|     return DeleteFile(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool MiiManager::DeleteFile() { |  | ||||||
|     const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + MII_SAVE_DATABASE_PATH; |  | ||||||
|     return FileUtil::Exists(path) && FileUtil::Delete(path); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MiiManager::WriteToFile() { |  | ||||||
|     const auto raw_path = |  | ||||||
|         FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000030"; |  | ||||||
|     if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path)) |  | ||||||
|         FileUtil::Delete(raw_path); |  | ||||||
|  |  | ||||||
|     const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + MII_SAVE_DATABASE_PATH; |  | ||||||
|  |  | ||||||
|     if (!FileUtil::CreateFullPath(path)) { |  | ||||||
|         LOG_WARNING(Service_Mii, |  | ||||||
|                     "Failed to create full path of MiiDatabase.dat. Create the directory " |  | ||||||
|                     "nand/system/save/8000000000000030 to mitigate this " |  | ||||||
|                     "issue."); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     FileUtil::IOFile save(path, "wb"); |  | ||||||
|  |  | ||||||
|     if (!save.IsOpen()) { |  | ||||||
|         LOG_WARNING(Service_Mii, "Failed to write save data to file... No changes to user data " |  | ||||||
|                                  "made in current session will be saved."); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     save.Resize(sizeof(MiiDatabase)); |  | ||||||
|     if (save.WriteBytes(&database, sizeof(MiiDatabase)) != sizeof(MiiDatabase)) { |  | ||||||
|         LOG_WARNING(Service_Mii, "Failed to write all data to save file... Data may be malformed " |  | ||||||
|                                  "and/or regenerated on next run."); |  | ||||||
|         save.Resize(0); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MiiManager::ReadFromFile() { |  | ||||||
|     FileUtil::IOFile save( |  | ||||||
|         FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + MII_SAVE_DATABASE_PATH, "rb"); |  | ||||||
|  |  | ||||||
|     if (!save.IsOpen()) { |  | ||||||
|         LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new " |  | ||||||
|                                  "blank Mii database with no Miis."); |  | ||||||
|         std::memcpy(&database, &DEFAULT_MII_DATABASE, sizeof(MiiDatabase)); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (save.ReadBytes(&database, sizeof(MiiDatabase)) != sizeof(MiiDatabase)) { |  | ||||||
|         LOG_WARNING(Service_ACC, "MiiDatabase.dat is smaller than expected... Generating new blank " |  | ||||||
|                                  "Mii database with no Miis."); |  | ||||||
|         std::memcpy(&database, &DEFAULT_MII_DATABASE, sizeof(MiiDatabase)); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     EnsureDatabasePartition(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| MiiStoreData MiiManager::CreateMiiWithUniqueUUID() const { |  | ||||||
|     auto new_mii = DEFAULT_MII; |  | ||||||
|  |  | ||||||
|     do { |  | ||||||
|         new_mii.uuid = Common::UUID::Generate(); |  | ||||||
|     } while (IndexOf(new_mii.uuid) != INVALID_INDEX); |  | ||||||
|  |  | ||||||
|     return new_mii; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MiiManager::EnsureDatabasePartition() { |  | ||||||
|     std::stable_partition(database.miis.begin(), database.miis.end(), |  | ||||||
|                           [](const MiiStoreData& elem) { return elem.uuid; }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| } // namespace Service::Mii |  | ||||||
| @@ -1,273 +0,0 @@ | |||||||
| // Copyright 2018 yuzu emulator team |  | ||||||
| // Licensed under GPLv2 or any later version |  | ||||||
| // Refer to the license.txt file included. |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "common/bit_field.h" |  | ||||||
| #include "common/common_funcs.h" |  | ||||||
| #include "common/uuid.h" |  | ||||||
|  |  | ||||||
| namespace Service::Mii { |  | ||||||
|  |  | ||||||
| constexpr std::size_t MAX_MIIS{100}; |  | ||||||
| constexpr u32 INVALID_INDEX{0xFFFFFFFF}; |  | ||||||
|  |  | ||||||
| struct RandomParameters { |  | ||||||
|     u32 unknown_1{}; |  | ||||||
|     u32 unknown_2{}; |  | ||||||
|     u32 unknown_3{}; |  | ||||||
| }; |  | ||||||
| static_assert(sizeof(RandomParameters) == 0xC, "RandomParameters has incorrect size."); |  | ||||||
|  |  | ||||||
| enum class Source : u32 { |  | ||||||
|     Database = 0, |  | ||||||
|     Default = 1, |  | ||||||
|     Account = 2, |  | ||||||
|     Friend = 3, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| std::ostream& operator<<(std::ostream& os, Source source); |  | ||||||
|  |  | ||||||
| struct MiiInfo { |  | ||||||
|     Common::UUID uuid{Common::INVALID_UUID}; |  | ||||||
|     std::array<char16_t, 11> name{}; |  | ||||||
|     u8 font_region{}; |  | ||||||
|     u8 favorite_color{}; |  | ||||||
|     u8 gender{}; |  | ||||||
|     u8 height{}; |  | ||||||
|     u8 weight{}; |  | ||||||
|     u8 mii_type{}; |  | ||||||
|     u8 mii_region{}; |  | ||||||
|     u8 face_type{}; |  | ||||||
|     u8 face_color{}; |  | ||||||
|     u8 face_wrinkle{}; |  | ||||||
|     u8 face_makeup{}; |  | ||||||
|     u8 hair_type{}; |  | ||||||
|     u8 hair_color{}; |  | ||||||
|     bool hair_flip{}; |  | ||||||
|     u8 eye_type{}; |  | ||||||
|     u8 eye_color{}; |  | ||||||
|     u8 eye_scale{}; |  | ||||||
|     u8 eye_aspect_ratio{}; |  | ||||||
|     u8 eye_rotate{}; |  | ||||||
|     u8 eye_x{}; |  | ||||||
|     u8 eye_y{}; |  | ||||||
|     u8 eyebrow_type{}; |  | ||||||
|     u8 eyebrow_color{}; |  | ||||||
|     u8 eyebrow_scale{}; |  | ||||||
|     u8 eyebrow_aspect_ratio{}; |  | ||||||
|     u8 eyebrow_rotate{}; |  | ||||||
|     u8 eyebrow_x{}; |  | ||||||
|     u8 eyebrow_y{}; |  | ||||||
|     u8 nose_type{}; |  | ||||||
|     u8 nose_scale{}; |  | ||||||
|     u8 nose_y{}; |  | ||||||
|     u8 mouth_type{}; |  | ||||||
|     u8 mouth_color{}; |  | ||||||
|     u8 mouth_scale{}; |  | ||||||
|     u8 mouth_aspect_ratio{}; |  | ||||||
|     u8 mouth_y{}; |  | ||||||
|     u8 facial_hair_color{}; |  | ||||||
|     u8 beard_type{}; |  | ||||||
|     u8 mustache_type{}; |  | ||||||
|     u8 mustache_scale{}; |  | ||||||
|     u8 mustache_y{}; |  | ||||||
|     u8 glasses_type{}; |  | ||||||
|     u8 glasses_color{}; |  | ||||||
|     u8 glasses_scale{}; |  | ||||||
|     u8 glasses_y{}; |  | ||||||
|     u8 mole_type{}; |  | ||||||
|     u8 mole_scale{}; |  | ||||||
|     u8 mole_x{}; |  | ||||||
|     u8 mole_y{}; |  | ||||||
|     INSERT_PADDING_BYTES(1); |  | ||||||
|  |  | ||||||
|     std::u16string Name() const; |  | ||||||
| }; |  | ||||||
| static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size."); |  | ||||||
| static_assert(std::has_unique_object_representations_v<MiiInfo>, |  | ||||||
|               "All bits of MiiInfo must contribute to its value."); |  | ||||||
|  |  | ||||||
| bool operator==(const MiiInfo& lhs, const MiiInfo& rhs); |  | ||||||
| bool operator!=(const MiiInfo& lhs, const MiiInfo& rhs); |  | ||||||
|  |  | ||||||
| #pragma pack(push, 4) |  | ||||||
| struct MiiInfoElement { |  | ||||||
|     MiiInfo info{}; |  | ||||||
|     Source source{}; |  | ||||||
| }; |  | ||||||
| static_assert(sizeof(MiiInfoElement) == 0x5C, "MiiInfoElement has incorrect size."); |  | ||||||
|  |  | ||||||
| struct MiiStoreBitFields { |  | ||||||
|     union { |  | ||||||
|         u32 word_0{}; |  | ||||||
|  |  | ||||||
|         BitField<24, 8, u32> hair_type; |  | ||||||
|         BitField<23, 1, u32> mole_type; |  | ||||||
|         BitField<16, 7, u32> height; |  | ||||||
|         BitField<15, 1, u32> hair_flip; |  | ||||||
|         BitField<8, 7, u32> weight; |  | ||||||
|         BitField<0, 7, u32> hair_color; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     union { |  | ||||||
|         u32 word_1{}; |  | ||||||
|  |  | ||||||
|         BitField<31, 1, u32> gender; |  | ||||||
|         BitField<24, 7, u32> eye_color; |  | ||||||
|         BitField<16, 7, u32> eyebrow_color; |  | ||||||
|         BitField<8, 7, u32> mouth_color; |  | ||||||
|         BitField<0, 7, u32> facial_hair_color; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     union { |  | ||||||
|         u32 word_2{}; |  | ||||||
|  |  | ||||||
|         BitField<31, 1, u32> mii_type; |  | ||||||
|         BitField<24, 7, u32> glasses_color; |  | ||||||
|         BitField<22, 2, u32> font_region; |  | ||||||
|         BitField<16, 6, u32> eye_type; |  | ||||||
|         BitField<14, 2, u32> mii_region; |  | ||||||
|         BitField<8, 6, u32> mouth_type; |  | ||||||
|         BitField<5, 3, u32> glasses_scale; |  | ||||||
|         BitField<0, 5, u32> eye_y; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     union { |  | ||||||
|         u32 word_3{}; |  | ||||||
|  |  | ||||||
|         BitField<29, 3, u32> mustache_type; |  | ||||||
|         BitField<24, 5, u32> eyebrow_type; |  | ||||||
|         BitField<21, 3, u32> beard_type; |  | ||||||
|         BitField<16, 5, u32> nose_type; |  | ||||||
|         BitField<13, 3, u32> mouth_aspect; |  | ||||||
|         BitField<8, 5, u32> nose_y; |  | ||||||
|         BitField<5, 3, u32> eyebrow_aspect; |  | ||||||
|         BitField<0, 5, u32> mouth_y; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     union { |  | ||||||
|         u32 word_4{}; |  | ||||||
|  |  | ||||||
|         BitField<29, 3, u32> eye_rotate; |  | ||||||
|         BitField<24, 5, u32> mustache_y; |  | ||||||
|         BitField<21, 3, u32> eye_aspect; |  | ||||||
|         BitField<16, 5, u32> glasses_y; |  | ||||||
|         BitField<13, 3, u32> eye_scale; |  | ||||||
|         BitField<8, 5, u32> mole_x; |  | ||||||
|         BitField<0, 5, u32> mole_y; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     union { |  | ||||||
|         u32 word_5{}; |  | ||||||
|  |  | ||||||
|         BitField<24, 5, u32> glasses_type; |  | ||||||
|         BitField<20, 4, u32> face_type; |  | ||||||
|         BitField<16, 4, u32> favorite_color; |  | ||||||
|         BitField<12, 4, u32> face_wrinkle; |  | ||||||
|         BitField<8, 4, u32> face_color; |  | ||||||
|         BitField<4, 4, u32> eye_x; |  | ||||||
|         BitField<0, 4, u32> face_makeup; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     union { |  | ||||||
|         u32 word_6{}; |  | ||||||
|  |  | ||||||
|         BitField<28, 4, u32> eyebrow_rotate; |  | ||||||
|         BitField<24, 4, u32> eyebrow_scale; |  | ||||||
|         BitField<20, 4, u32> eyebrow_y; |  | ||||||
|         BitField<16, 4, u32> eyebrow_x; |  | ||||||
|         BitField<12, 4, u32> mouth_scale; |  | ||||||
|         BitField<8, 4, u32> nose_scale; |  | ||||||
|         BitField<4, 4, u32> mole_scale; |  | ||||||
|         BitField<0, 4, u32> mustache_scale; |  | ||||||
|     }; |  | ||||||
| }; |  | ||||||
| static_assert(sizeof(MiiStoreBitFields) == 0x1C, "MiiStoreBitFields has incorrect size."); |  | ||||||
| static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>, |  | ||||||
|               "MiiStoreBitFields is not trivially copyable."); |  | ||||||
|  |  | ||||||
| struct MiiStoreData { |  | ||||||
|     // This corresponds to the above structure MiiStoreBitFields. I did it like this because the |  | ||||||
|     // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is |  | ||||||
|     // not suitable for our uses. |  | ||||||
|     std::array<u8, 0x1C> data{}; |  | ||||||
|     static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size."); |  | ||||||
|  |  | ||||||
|     std::array<char16_t, 10> name{}; |  | ||||||
|     Common::UUID uuid{Common::INVALID_UUID}; |  | ||||||
|     u16 crc_1{}; |  | ||||||
|     u16 crc_2{}; |  | ||||||
|  |  | ||||||
|     std::u16string Name() const; |  | ||||||
| }; |  | ||||||
| static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size."); |  | ||||||
|  |  | ||||||
| struct MiiStoreDataElement { |  | ||||||
|     MiiStoreData data{}; |  | ||||||
|     Source source{}; |  | ||||||
| }; |  | ||||||
| static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size."); |  | ||||||
|  |  | ||||||
| struct MiiDatabase { |  | ||||||
|     u32 magic{}; // 'NFDB' |  | ||||||
|     std::array<MiiStoreData, MAX_MIIS> miis{}; |  | ||||||
|     INSERT_PADDING_BYTES(1); |  | ||||||
|     u8 count{}; |  | ||||||
|     u16 crc{}; |  | ||||||
| }; |  | ||||||
| static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size."); |  | ||||||
| #pragma pack(pop) |  | ||||||
|  |  | ||||||
| // The Mii manager is responsible for loading and storing the Miis to the database in NAND along |  | ||||||
| // with providing an easy interface for HLE emulation of the mii service. |  | ||||||
| class MiiManager { |  | ||||||
| public: |  | ||||||
|     MiiManager(); |  | ||||||
|     ~MiiManager(); |  | ||||||
|  |  | ||||||
|     MiiInfo CreateRandom(RandomParameters params); |  | ||||||
|     MiiInfo CreateDefault(u32 index); |  | ||||||
|  |  | ||||||
|     bool CheckUpdatedFlag() const; |  | ||||||
|     void ResetUpdatedFlag(); |  | ||||||
|  |  | ||||||
|     bool IsTestModeEnabled() const; |  | ||||||
|  |  | ||||||
|     bool Empty() const; |  | ||||||
|     bool Full() const; |  | ||||||
|  |  | ||||||
|     void Clear(); |  | ||||||
|  |  | ||||||
|     u32 Size() const; |  | ||||||
|  |  | ||||||
|     MiiInfo GetInfo(u32 index) const; |  | ||||||
|     MiiInfoElement GetInfoElement(u32 index) const; |  | ||||||
|     MiiStoreData GetStoreData(u32 index) const; |  | ||||||
|     MiiStoreDataElement GetStoreDataElement(u32 index) const; |  | ||||||
|  |  | ||||||
|     bool Remove(Common::UUID uuid); |  | ||||||
|     u32 IndexOf(Common::UUID uuid) const; |  | ||||||
|     u32 IndexOf(const MiiInfo& info) const; |  | ||||||
|  |  | ||||||
|     bool Move(Common::UUID uuid, u32 new_index); |  | ||||||
|     bool AddOrReplace(const MiiStoreData& data); |  | ||||||
|  |  | ||||||
|     bool DestroyFile(); |  | ||||||
|     bool DeleteFile(); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     void WriteToFile(); |  | ||||||
|     void ReadFromFile(); |  | ||||||
|  |  | ||||||
|     MiiStoreData CreateMiiWithUniqueUUID() const; |  | ||||||
|  |  | ||||||
|     void EnsureDatabasePartition(); |  | ||||||
|  |  | ||||||
|     MiiDatabase database; |  | ||||||
|     bool updated_flag{}; |  | ||||||
|     bool is_test_mode_enabled{}; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| }; // namespace Service::Mii |  | ||||||
							
								
								
									
										2261
									
								
								src/core/hle/service/mii/raw_data.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2261
									
								
								src/core/hle/service/mii/raw_data.cpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										27
									
								
								src/core/hle/service/mii/raw_data.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/core/hle/service/mii/raw_data.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // Copyright 2020 yuzu Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
|  |  | ||||||
|  | #include "common/common_types.h" | ||||||
|  |  | ||||||
|  | namespace Service::Mii::RawData { | ||||||
|  |  | ||||||
|  | extern const std::array<u8, 1728> DefaultMii; | ||||||
|  | extern const std::array<u8, 3672> RandomMiiFaceline; | ||||||
|  | extern const std::array<u8, 1200> RandomMiiFacelineColor; | ||||||
|  | extern const std::array<u8, 3672> RandomMiiFacelineWrinkle; | ||||||
|  | extern const std::array<u8, 3672> RandomMiiFacelineMakeup; | ||||||
|  | extern const std::array<u8, 3672> RandomMiiHairType; | ||||||
|  | extern const std::array<u8, 1800> RandomMiiHairColor; | ||||||
|  | extern const std::array<u8, 3672> RandomMiiEyeType; | ||||||
|  | extern const std::array<u8, 588> RandomMiiEyeColor; | ||||||
|  | extern const std::array<u8, 3672> RandomMiiEyebrowType; | ||||||
|  | extern const std::array<u8, 3672> RandomMiiNoseType; | ||||||
|  | extern const std::array<u8, 3672> RandomMiiMouthType; | ||||||
|  | extern const std::array<u8, 588> RandomMiiGlassType; | ||||||
|  |  | ||||||
|  | } // namespace Service::Mii::RawData | ||||||
							
								
								
									
										67
									
								
								src/core/hle/service/mii/types.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/core/hle/service/mii/types.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | // Copyright 2020 yuzu emulator team | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "common/common_funcs.h" | ||||||
|  | #include "common/common_types.h" | ||||||
|  |  | ||||||
|  | namespace Service::Mii { | ||||||
|  |  | ||||||
|  | enum class Age : u32 { | ||||||
|  |     Young, | ||||||
|  |     Normal, | ||||||
|  |     Old, | ||||||
|  |     All, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class BeardType : u32 { | ||||||
|  |     None, | ||||||
|  |     Beard1, | ||||||
|  |     Beard2, | ||||||
|  |     Beard3, | ||||||
|  |     Beard4, | ||||||
|  |     Beard5, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class BeardAndMustacheFlag : u32 { Beard = 1, Mustache, All = Beard | Mustache }; | ||||||
|  | DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag); | ||||||
|  |  | ||||||
|  | enum class FontRegion : u32 { | ||||||
|  |     Standard, | ||||||
|  |     China, | ||||||
|  |     Korea, | ||||||
|  |     Taiwan, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class Gender : u32 { | ||||||
|  |     Male, | ||||||
|  |     Female, | ||||||
|  |     All, | ||||||
|  |     Maximum = Female, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class HairFlip : u32 { | ||||||
|  |     Left, | ||||||
|  |     Right, | ||||||
|  |     Maximum = Right, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class MustacheType : u32 { | ||||||
|  |     None, | ||||||
|  |     Mustache1, | ||||||
|  |     Mustache2, | ||||||
|  |     Mustache3, | ||||||
|  |     Mustache4, | ||||||
|  |     Mustache5, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class Race : u32 { | ||||||
|  |     Black, | ||||||
|  |     White, | ||||||
|  |     Asian, | ||||||
|  |     All, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace Service::Mii | ||||||
		Reference in New Issue
	
	Block a user