diff options
| -rw-r--r-- | src/core/crypto/key_manager.cpp | 226 | ||||
| -rw-r--r-- | src/core/crypto/key_manager.h | 59 | ||||
| -rw-r--r-- | src/core/file_sys/submission_package.cpp | 35 | ||||
| -rw-r--r-- | src/core/file_sys/submission_package.h | 1 | ||||
| -rw-r--r-- | src/core/hle/service/es/es.cpp | 10 | ||||
| -rw-r--r-- | src/core/hle/service/mii/mii_manager.cpp | 2 | ||||
| -rw-r--r-- | src/core/hle/service/mii/raw_data.cpp | 104 | ||||
| -rw-r--r-- | src/core/hle/service/mii/raw_data.h | 2 |
8 files changed, 189 insertions, 250 deletions
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 4ff2c50e5..e13c5cdc7 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp | |||
| @@ -35,7 +35,6 @@ namespace Core::Crypto { | |||
| 35 | namespace { | 35 | namespace { |
| 36 | 36 | ||
| 37 | constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; | 37 | constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; |
| 38 | constexpr u64 FULL_TICKET_SIZE = 0x400; | ||
| 39 | 38 | ||
| 40 | using Common::AsArray; | 39 | using Common::AsArray; |
| 41 | 40 | ||
| @@ -156,6 +155,10 @@ u64 GetSignatureTypePaddingSize(SignatureType type) { | |||
| 156 | UNREACHABLE(); | 155 | UNREACHABLE(); |
| 157 | } | 156 | } |
| 158 | 157 | ||
| 158 | bool Ticket::IsValid() const { | ||
| 159 | return !std::holds_alternative<std::monostate>(data); | ||
| 160 | } | ||
| 161 | |||
| 159 | SignatureType Ticket::GetSignatureType() const { | 162 | SignatureType Ticket::GetSignatureType() const { |
| 160 | if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) { | 163 | if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) { |
| 161 | return ticket->sig_type; | 164 | return ticket->sig_type; |
| @@ -210,6 +213,54 @@ Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& righ | |||
| 210 | return Ticket{out}; | 213 | return Ticket{out}; |
| 211 | } | 214 | } |
| 212 | 215 | ||
| 216 | Ticket Ticket::Read(const FileSys::VirtualFile& file) { | ||
| 217 | // Attempt to read up to the largest ticket size, and make sure we read at least a signature | ||
| 218 | // type. | ||
| 219 | std::array<u8, sizeof(RSA4096Ticket)> raw_data{}; | ||
| 220 | auto read_size = file->Read(raw_data.data(), raw_data.size(), 0); | ||
| 221 | if (read_size < sizeof(SignatureType)) { | ||
| 222 | LOG_WARNING(Crypto, "Attempted to read ticket file with invalid size {}.", read_size); | ||
| 223 | return Ticket{std::monostate()}; | ||
| 224 | } | ||
| 225 | return Read(std::span{raw_data}); | ||
| 226 | } | ||
| 227 | |||
| 228 | Ticket Ticket::Read(std::span<const u8> raw_data) { | ||
| 229 | // Some tools read only 0x180 bytes of ticket data instead of 0x2C0, so | ||
| 230 | // just make sure we have at least the bare minimum of data to work with. | ||
| 231 | SignatureType sig_type; | ||
| 232 | if (raw_data.size() < sizeof(SignatureType)) { | ||
| 233 | LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid size {}.", | ||
| 234 | raw_data.size()); | ||
| 235 | return Ticket{std::monostate()}; | ||
| 236 | } | ||
| 237 | std::memcpy(&sig_type, raw_data.data(), sizeof(sig_type)); | ||
| 238 | |||
| 239 | switch (sig_type) { | ||
| 240 | case SignatureType::RSA_4096_SHA1: | ||
| 241 | case SignatureType::RSA_4096_SHA256: { | ||
| 242 | RSA4096Ticket ticket{}; | ||
| 243 | std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); | ||
| 244 | return Ticket{ticket}; | ||
| 245 | } | ||
| 246 | case SignatureType::RSA_2048_SHA1: | ||
| 247 | case SignatureType::RSA_2048_SHA256: { | ||
| 248 | RSA2048Ticket ticket{}; | ||
| 249 | std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); | ||
| 250 | return Ticket{ticket}; | ||
| 251 | } | ||
| 252 | case SignatureType::ECDSA_SHA1: | ||
| 253 | case SignatureType::ECDSA_SHA256: { | ||
| 254 | ECDSATicket ticket{}; | ||
| 255 | std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); | ||
| 256 | return Ticket{ticket}; | ||
| 257 | } | ||
| 258 | default: | ||
| 259 | LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid type {}.", sig_type); | ||
| 260 | return Ticket{std::monostate()}; | ||
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 213 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { | 264 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { |
| 214 | Key128 out{}; | 265 | Key128 out{}; |
| 215 | 266 | ||
| @@ -290,9 +341,9 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { | |||
| 290 | } | 341 | } |
| 291 | } | 342 | } |
| 292 | 343 | ||
| 293 | RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { | 344 | void KeyManager::DeriveETicketRSAKey() { |
| 294 | if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) { | 345 | if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) { |
| 295 | return {}; | 346 | return; |
| 296 | } | 347 | } |
| 297 | 348 | ||
| 298 | const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); | 349 | const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); |
| @@ -304,12 +355,12 @@ RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { | |||
| 304 | rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, | 355 | rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, |
| 305 | extended_dec.data(), Op::Decrypt); | 356 | extended_dec.data(), Op::Decrypt); |
| 306 | 357 | ||
| 307 | RSAKeyPair<2048> rsa_key{}; | 358 | std::memcpy(eticket_rsa_keypair.decryption_key.data(), extended_dec.data(), |
| 308 | std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); | 359 | eticket_rsa_keypair.decryption_key.size()); |
| 309 | std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); | 360 | std::memcpy(eticket_rsa_keypair.modulus.data(), extended_dec.data() + 0x100, |
| 310 | std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); | 361 | eticket_rsa_keypair.modulus.size()); |
| 311 | 362 | std::memcpy(eticket_rsa_keypair.exponent.data(), extended_dec.data() + 0x200, | |
| 312 | return rsa_key; | 363 | eticket_rsa_keypair.exponent.size()); |
| 313 | } | 364 | } |
| 314 | 365 | ||
| 315 | Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { | 366 | Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { |
| @@ -447,10 +498,12 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) { | |||
| 447 | for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { | 498 | for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { |
| 448 | if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && | 499 | if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && |
| 449 | buffer[offset + 3] == 0x0) { | 500 | buffer[offset + 3] == 0x0) { |
| 450 | out.emplace_back(); | 501 | // NOTE: Assumes ticket blob will only contain RSA-2048 tickets. |
| 451 | auto& next = out.back(); | 502 | auto ticket = Ticket::Read(std::span{buffer.data() + offset, sizeof(RSA2048Ticket)}); |
| 452 | std::memcpy(&next, buffer.data() + offset, sizeof(Ticket)); | 503 | offset += sizeof(RSA2048Ticket); |
| 453 | offset += FULL_TICKET_SIZE; | 504 | if (ticket.IsValid()) { |
| 505 | out.push_back(ticket); | ||
| 506 | } | ||
| 454 | } | 507 | } |
| 455 | } | 508 | } |
| 456 | 509 | ||
| @@ -503,25 +556,36 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { | |||
| 503 | return offset; | 556 | return offset; |
| 504 | } | 557 | } |
| 505 | 558 | ||
| 506 | std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | 559 | std::optional<Key128> KeyManager::ParseTicketTitleKey(const Ticket& ticket) { |
| 507 | const RSAKeyPair<2048>& key) { | 560 | if (!ticket.IsValid()) { |
| 561 | LOG_WARNING(Crypto, "Attempted to parse title key of invalid ticket."); | ||
| 562 | return std::nullopt; | ||
| 563 | } | ||
| 564 | |||
| 565 | if (ticket.GetData().rights_id == Key128{}) { | ||
| 566 | LOG_WARNING(Crypto, "Attempted to parse title key of ticket with no rights ID."); | ||
| 567 | return std::nullopt; | ||
| 568 | } | ||
| 569 | |||
| 508 | const auto issuer = ticket.GetData().issuer; | 570 | const auto issuer = ticket.GetData().issuer; |
| 509 | if (IsAllZeroArray(issuer)) { | 571 | if (IsAllZeroArray(issuer)) { |
| 572 | LOG_WARNING(Crypto, "Attempted to parse title key of ticket with invalid issuer."); | ||
| 510 | return std::nullopt; | 573 | return std::nullopt; |
| 511 | } | 574 | } |
| 575 | |||
| 512 | if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { | 576 | if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { |
| 513 | LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); | 577 | LOG_WARNING(Crypto, "Parsing ticket with non-standard certificate authority."); |
| 514 | } | 578 | } |
| 515 | 579 | ||
| 516 | Key128 rights_id = ticket.GetData().rights_id; | 580 | if (ticket.GetData().type == TitleKeyType::Common) { |
| 517 | 581 | return ticket.GetData().title_key_common; | |
| 518 | if (rights_id == Key128{}) { | ||
| 519 | return std::nullopt; | ||
| 520 | } | 582 | } |
| 521 | 583 | ||
| 522 | if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), | 584 | if (eticket_rsa_keypair == RSAKeyPair<2048>{}) { |
| 523 | ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { | 585 | LOG_WARNING( |
| 524 | return std::make_pair(rights_id, ticket.GetData().title_key_common); | 586 | Crypto, |
| 587 | "Skipping personalized ticket title key parsing due to missing ETicket RSA key-pair."); | ||
| 588 | return std::nullopt; | ||
| 525 | } | 589 | } |
| 526 | 590 | ||
| 527 | mbedtls_mpi D; // RSA Private Exponent | 591 | mbedtls_mpi D; // RSA Private Exponent |
| @@ -534,9 +598,12 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | |||
| 534 | mbedtls_mpi_init(&S); | 598 | mbedtls_mpi_init(&S); |
| 535 | mbedtls_mpi_init(&M); | 599 | mbedtls_mpi_init(&M); |
| 536 | 600 | ||
| 537 | mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); | 601 | const auto& title_key_block = ticket.GetData().title_key_block; |
| 538 | mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); | 602 | mbedtls_mpi_read_binary(&D, eticket_rsa_keypair.decryption_key.data(), |
| 539 | mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100); | 603 | eticket_rsa_keypair.decryption_key.size()); |
| 604 | mbedtls_mpi_read_binary(&N, eticket_rsa_keypair.modulus.data(), | ||
| 605 | eticket_rsa_keypair.modulus.size()); | ||
| 606 | mbedtls_mpi_read_binary(&S, title_key_block.data(), title_key_block.size()); | ||
| 540 | 607 | ||
| 541 | mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); | 608 | mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); |
| 542 | 609 | ||
| @@ -564,8 +631,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | |||
| 564 | 631 | ||
| 565 | Key128 key_temp{}; | 632 | Key128 key_temp{}; |
| 566 | std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); | 633 | std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); |
| 567 | 634 | return key_temp; | |
| 568 | return std::make_pair(rights_id, key_temp); | ||
| 569 | } | 635 | } |
| 570 | 636 | ||
| 571 | KeyManager::KeyManager() { | 637 | KeyManager::KeyManager() { |
| @@ -669,6 +735,14 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti | |||
| 669 | encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); | 735 | encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); |
| 670 | } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { | 736 | } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { |
| 671 | eticket_extended_kek = Common::HexStringToArray<576>(out[1]); | 737 | eticket_extended_kek = Common::HexStringToArray<576>(out[1]); |
| 738 | } else if (out[0].compare(0, 19, "eticket_rsa_keypair") == 0) { | ||
| 739 | const auto key_data = Common::HexStringToArray<528>(out[1]); | ||
| 740 | std::memcpy(eticket_rsa_keypair.decryption_key.data(), key_data.data(), | ||
| 741 | eticket_rsa_keypair.decryption_key.size()); | ||
| 742 | std::memcpy(eticket_rsa_keypair.modulus.data(), key_data.data() + 0x100, | ||
| 743 | eticket_rsa_keypair.modulus.size()); | ||
| 744 | std::memcpy(eticket_rsa_keypair.exponent.data(), key_data.data() + 0x200, | ||
| 745 | eticket_rsa_keypair.exponent.size()); | ||
| 672 | } else { | 746 | } else { |
| 673 | for (const auto& kv : KEYS_VARIABLE_LENGTH) { | 747 | for (const auto& kv : KEYS_VARIABLE_LENGTH) { |
| 674 | if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) { | 748 | if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) { |
| @@ -1110,56 +1184,38 @@ void KeyManager::DeriveETicket(PartitionDataManager& data, | |||
| 1110 | 1184 | ||
| 1111 | eticket_extended_kek = data.GetETicketExtendedKek(); | 1185 | eticket_extended_kek = data.GetETicketExtendedKek(); |
| 1112 | WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); | 1186 | WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); |
| 1187 | DeriveETicketRSAKey(); | ||
| 1113 | PopulateTickets(); | 1188 | PopulateTickets(); |
| 1114 | } | 1189 | } |
| 1115 | 1190 | ||
| 1116 | void KeyManager::PopulateTickets() { | 1191 | void KeyManager::PopulateTickets() { |
| 1117 | const auto rsa_key = GetETicketRSAKey(); | 1192 | if (ticket_databases_loaded) { |
| 1118 | |||
| 1119 | if (rsa_key == RSAKeyPair<2048>{}) { | ||
| 1120 | return; | 1193 | return; |
| 1121 | } | 1194 | } |
| 1195 | ticket_databases_loaded = true; | ||
| 1122 | 1196 | ||
| 1123 | if (!common_tickets.empty() && !personal_tickets.empty()) { | 1197 | std::vector<Ticket> tickets; |
| 1124 | return; | ||
| 1125 | } | ||
| 1126 | 1198 | ||
| 1127 | const auto system_save_e1_path = | 1199 | const auto system_save_e1_path = |
| 1128 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1"; | 1200 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1"; |
| 1129 | 1201 | if (Common::FS::Exists(system_save_e1_path)) { | |
| 1130 | const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, | 1202 | const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, |
| 1131 | Common::FS::FileType::BinaryFile}; | 1203 | Common::FS::FileType::BinaryFile}; |
| 1204 | const auto blob1 = GetTicketblob(save_e1); | ||
| 1205 | tickets.insert(tickets.end(), blob1.begin(), blob1.end()); | ||
| 1206 | } | ||
| 1132 | 1207 | ||
| 1133 | const auto system_save_e2_path = | 1208 | const auto system_save_e2_path = |
| 1134 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2"; | 1209 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2"; |
| 1210 | if (Common::FS::Exists(system_save_e2_path)) { | ||
| 1211 | const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, | ||
| 1212 | Common::FS::FileType::BinaryFile}; | ||
| 1213 | const auto blob2 = GetTicketblob(save_e2); | ||
| 1214 | tickets.insert(tickets.end(), blob2.begin(), blob2.end()); | ||
| 1215 | } | ||
| 1135 | 1216 | ||
| 1136 | const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, | 1217 | for (const auto& ticket : tickets) { |
| 1137 | Common::FS::FileType::BinaryFile}; | 1218 | AddTicket(ticket); |
| 1138 | |||
| 1139 | const auto blob2 = GetTicketblob(save_e2); | ||
| 1140 | auto res = GetTicketblob(save_e1); | ||
| 1141 | |||
| 1142 | const auto idx = res.size(); | ||
| 1143 | res.insert(res.end(), blob2.begin(), blob2.end()); | ||
| 1144 | |||
| 1145 | for (std::size_t i = 0; i < res.size(); ++i) { | ||
| 1146 | const auto common = i < idx; | ||
| 1147 | const auto pair = ParseTicket(res[i], rsa_key); | ||
| 1148 | if (!pair) { | ||
| 1149 | continue; | ||
| 1150 | } | ||
| 1151 | |||
| 1152 | const auto& [rid, key] = *pair; | ||
| 1153 | u128 rights_id; | ||
| 1154 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | ||
| 1155 | |||
| 1156 | if (common) { | ||
| 1157 | common_tickets[rights_id] = res[i]; | ||
| 1158 | } else { | ||
| 1159 | personal_tickets[rights_id] = res[i]; | ||
| 1160 | } | ||
| 1161 | |||
| 1162 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 1163 | } | 1219 | } |
| 1164 | } | 1220 | } |
| 1165 | 1221 | ||
| @@ -1291,41 +1347,33 @@ const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const { | |||
| 1291 | return personal_tickets; | 1347 | return personal_tickets; |
| 1292 | } | 1348 | } |
| 1293 | 1349 | ||
| 1294 | bool KeyManager::AddTicketCommon(Ticket raw) { | 1350 | bool KeyManager::AddTicket(const Ticket& ticket) { |
| 1295 | const auto rsa_key = GetETicketRSAKey(); | 1351 | if (!ticket.IsValid()) { |
| 1296 | if (rsa_key == RSAKeyPair<2048>{}) { | 1352 | LOG_WARNING(Crypto, "Attempted to add invalid ticket."); |
| 1297 | return false; | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | const auto pair = ParseTicket(raw, rsa_key); | ||
| 1301 | if (!pair) { | ||
| 1302 | return false; | 1353 | return false; |
| 1303 | } | 1354 | } |
| 1304 | 1355 | ||
| 1305 | const auto& [rid, key] = *pair; | 1356 | const auto& rid = ticket.GetData().rights_id; |
| 1306 | u128 rights_id; | 1357 | u128 rights_id; |
| 1307 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | 1358 | std::memcpy(rights_id.data(), rid.data(), rid.size()); |
| 1308 | common_tickets[rights_id] = raw; | 1359 | if (ticket.GetData().type == Core::Crypto::TitleKeyType::Common) { |
| 1309 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | 1360 | common_tickets[rights_id] = ticket; |
| 1310 | return true; | 1361 | } else { |
| 1311 | } | 1362 | personal_tickets[rights_id] = ticket; |
| 1363 | } | ||
| 1312 | 1364 | ||
| 1313 | bool KeyManager::AddTicketPersonalized(Ticket raw) { | 1365 | if (HasKey(S128KeyType::Titlekey, rights_id[1], rights_id[0])) { |
| 1314 | const auto rsa_key = GetETicketRSAKey(); | 1366 | LOG_DEBUG(Crypto, |
| 1315 | if (rsa_key == RSAKeyPair<2048>{}) { | 1367 | "Skipping parsing title key from ticket for known rights ID {:016X}{:016X}.", |
| 1316 | return false; | 1368 | rights_id[1], rights_id[0]); |
| 1369 | return true; | ||
| 1317 | } | 1370 | } |
| 1318 | 1371 | ||
| 1319 | const auto pair = ParseTicket(raw, rsa_key); | 1372 | const auto key = ParseTicketTitleKey(ticket); |
| 1320 | if (!pair) { | 1373 | if (!key) { |
| 1321 | return false; | 1374 | return false; |
| 1322 | } | 1375 | } |
| 1323 | 1376 | SetKey(S128KeyType::Titlekey, key.value(), rights_id[1], rights_id[0]); | |
| 1324 | const auto& [rid, key] = *pair; | ||
| 1325 | u128 rights_id; | ||
| 1326 | std::memcpy(rights_id.data(), rid.data(), rid.size()); | ||
| 1327 | common_tickets[rights_id] = raw; | ||
| 1328 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 1329 | return true; | 1377 | return true; |
| 1330 | } | 1378 | } |
| 1331 | } // namespace Core::Crypto | 1379 | } // namespace Core::Crypto |
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 8c864503b..2250eccec 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include <filesystem> | 7 | #include <filesystem> |
| 8 | #include <map> | 8 | #include <map> |
| 9 | #include <optional> | 9 | #include <optional> |
| 10 | #include <span> | ||
| 10 | #include <string> | 11 | #include <string> |
| 11 | 12 | ||
| 12 | #include <variant> | 13 | #include <variant> |
| @@ -29,8 +30,6 @@ enum class ResultStatus : u16; | |||
| 29 | 30 | ||
| 30 | namespace Core::Crypto { | 31 | namespace Core::Crypto { |
| 31 | 32 | ||
| 32 | constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; | ||
| 33 | |||
| 34 | using Key128 = std::array<u8, 0x10>; | 33 | using Key128 = std::array<u8, 0x10>; |
| 35 | using Key256 = std::array<u8, 0x20>; | 34 | using Key256 = std::array<u8, 0x20>; |
| 36 | using SHA256Hash = std::array<u8, 0x20>; | 35 | using SHA256Hash = std::array<u8, 0x20>; |
| @@ -82,6 +81,7 @@ struct RSA4096Ticket { | |||
| 82 | INSERT_PADDING_BYTES(0x3C); | 81 | INSERT_PADDING_BYTES(0x3C); |
| 83 | TicketData data; | 82 | TicketData data; |
| 84 | }; | 83 | }; |
| 84 | static_assert(sizeof(RSA4096Ticket) == 0x500, "RSA4096Ticket has incorrect size."); | ||
| 85 | 85 | ||
| 86 | struct RSA2048Ticket { | 86 | struct RSA2048Ticket { |
| 87 | SignatureType sig_type; | 87 | SignatureType sig_type; |
| @@ -89,6 +89,7 @@ struct RSA2048Ticket { | |||
| 89 | INSERT_PADDING_BYTES(0x3C); | 89 | INSERT_PADDING_BYTES(0x3C); |
| 90 | TicketData data; | 90 | TicketData data; |
| 91 | }; | 91 | }; |
| 92 | static_assert(sizeof(RSA2048Ticket) == 0x400, "RSA2048Ticket has incorrect size."); | ||
| 92 | 93 | ||
| 93 | struct ECDSATicket { | 94 | struct ECDSATicket { |
| 94 | SignatureType sig_type; | 95 | SignatureType sig_type; |
| @@ -96,16 +97,41 @@ struct ECDSATicket { | |||
| 96 | INSERT_PADDING_BYTES(0x40); | 97 | INSERT_PADDING_BYTES(0x40); |
| 97 | TicketData data; | 98 | TicketData data; |
| 98 | }; | 99 | }; |
| 100 | static_assert(sizeof(ECDSATicket) == 0x340, "ECDSATicket has incorrect size."); | ||
| 99 | 101 | ||
| 100 | struct Ticket { | 102 | struct Ticket { |
| 101 | std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data; | 103 | std::variant<std::monostate, RSA4096Ticket, RSA2048Ticket, ECDSATicket> data; |
| 102 | 104 | ||
| 103 | SignatureType GetSignatureType() const; | 105 | [[nodiscard]] bool IsValid() const; |
| 104 | TicketData& GetData(); | 106 | [[nodiscard]] SignatureType GetSignatureType() const; |
| 105 | const TicketData& GetData() const; | 107 | [[nodiscard]] TicketData& GetData(); |
| 106 | u64 GetSize() const; | 108 | [[nodiscard]] const TicketData& GetData() const; |
| 107 | 109 | [[nodiscard]] u64 GetSize() const; | |
| 110 | |||
| 111 | /** | ||
| 112 | * Synthesizes a common ticket given a title key and rights ID. | ||
| 113 | * | ||
| 114 | * @param title_key Title key to store in the ticket. | ||
| 115 | * @param rights_id Rights ID the ticket is for. | ||
| 116 | * @return The synthesized common ticket. | ||
| 117 | */ | ||
| 108 | static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id); | 118 | static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id); |
| 119 | |||
| 120 | /** | ||
| 121 | * Reads a ticket from a file. | ||
| 122 | * | ||
| 123 | * @param file File to read the ticket from. | ||
| 124 | * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false. | ||
| 125 | */ | ||
| 126 | static Ticket Read(const FileSys::VirtualFile& file); | ||
| 127 | |||
| 128 | /** | ||
| 129 | * Reads a ticket from a memory buffer. | ||
| 130 | * | ||
| 131 | * @param raw_data Buffer to read the ticket from. | ||
| 132 | * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false. | ||
| 133 | */ | ||
| 134 | static Ticket Read(std::span<const u8> raw_data); | ||
| 109 | }; | 135 | }; |
| 110 | 136 | ||
| 111 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); | 137 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); |
| @@ -264,8 +290,7 @@ public: | |||
| 264 | const std::map<u128, Ticket>& GetCommonTickets() const; | 290 | const std::map<u128, Ticket>& GetCommonTickets() const; |
| 265 | const std::map<u128, Ticket>& GetPersonalizedTickets() const; | 291 | const std::map<u128, Ticket>& GetPersonalizedTickets() const; |
| 266 | 292 | ||
| 267 | bool AddTicketCommon(Ticket raw); | 293 | bool AddTicket(const Ticket& ticket); |
| 268 | bool AddTicketPersonalized(Ticket raw); | ||
| 269 | 294 | ||
| 270 | void ReloadKeys(); | 295 | void ReloadKeys(); |
| 271 | bool AreKeysLoaded() const; | 296 | bool AreKeysLoaded() const; |
| @@ -279,10 +304,12 @@ private: | |||
| 279 | // Map from rights ID to ticket | 304 | // Map from rights ID to ticket |
| 280 | std::map<u128, Ticket> common_tickets; | 305 | std::map<u128, Ticket> common_tickets; |
| 281 | std::map<u128, Ticket> personal_tickets; | 306 | std::map<u128, Ticket> personal_tickets; |
| 307 | bool ticket_databases_loaded = false; | ||
| 282 | 308 | ||
| 283 | std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; | 309 | std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; |
| 284 | std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; | 310 | std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; |
| 285 | std::array<u8, 576> eticket_extended_kek{}; | 311 | std::array<u8, 576> eticket_extended_kek{}; |
| 312 | RSAKeyPair<2048> eticket_rsa_keypair{}; | ||
| 286 | 313 | ||
| 287 | bool dev_mode; | 314 | bool dev_mode; |
| 288 | void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys); | 315 | void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys); |
| @@ -293,10 +320,13 @@ private: | |||
| 293 | 320 | ||
| 294 | void DeriveGeneralPurposeKeys(std::size_t crypto_revision); | 321 | void DeriveGeneralPurposeKeys(std::size_t crypto_revision); |
| 295 | 322 | ||
| 296 | RSAKeyPair<2048> GetETicketRSAKey() const; | 323 | void DeriveETicketRSAKey(); |
| 297 | 324 | ||
| 298 | void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); | 325 | void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); |
| 299 | void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); | 326 | void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); |
| 327 | |||
| 328 | /// Parses the title key section of a ticket. | ||
| 329 | std::optional<Key128> ParseTicketTitleKey(const Ticket& ticket); | ||
| 300 | }; | 330 | }; |
| 301 | 331 | ||
| 302 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); | 332 | Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); |
| @@ -311,9 +341,4 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke | |||
| 311 | 341 | ||
| 312 | std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save); | 342 | std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save); |
| 313 | 343 | ||
| 314 | // Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority | ||
| 315 | // (offset 0x140-0x144 is zero) | ||
| 316 | std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, | ||
| 317 | const RSAKeyPair<2048>& eticket_extended_key); | ||
| 318 | |||
| 319 | } // namespace Core::Crypto | 344 | } // namespace Core::Crypto |
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index e1e89ce2d..68e8ec22f 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp | |||
| @@ -164,24 +164,6 @@ VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType titl | |||
| 164 | return nullptr; | 164 | return nullptr; |
| 165 | } | 165 | } |
| 166 | 166 | ||
| 167 | std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const { | ||
| 168 | if (extracted) | ||
| 169 | LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); | ||
| 170 | std::vector<Core::Crypto::Key128> out; | ||
| 171 | for (const auto& ticket_file : ticket_files) { | ||
| 172 | if (ticket_file == nullptr || | ||
| 173 | ticket_file->GetSize() < | ||
| 174 | Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { | ||
| 175 | continue; | ||
| 176 | } | ||
| 177 | |||
| 178 | out.emplace_back(); | ||
| 179 | ticket_file->Read(out.back().data(), out.back().size(), | ||
| 180 | Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); | ||
| 181 | } | ||
| 182 | return out; | ||
| 183 | } | ||
| 184 | |||
| 185 | std::vector<VirtualFile> NSP::GetFiles() const { | 167 | std::vector<VirtualFile> NSP::GetFiles() const { |
| 186 | return pfs->GetFiles(); | 168 | return pfs->GetFiles(); |
| 187 | } | 169 | } |
| @@ -208,22 +190,11 @@ void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) { | |||
| 208 | continue; | 190 | continue; |
| 209 | } | 191 | } |
| 210 | 192 | ||
| 211 | if (ticket_file->GetSize() < | 193 | auto ticket = Core::Crypto::Ticket::Read(ticket_file); |
| 212 | Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { | 194 | if (!keys.AddTicket(ticket)) { |
| 195 | LOG_WARNING(Common_Filesystem, "Could not load NSP ticket {}", ticket_file->GetName()); | ||
| 213 | continue; | 196 | continue; |
| 214 | } | 197 | } |
| 215 | |||
| 216 | Core::Crypto::Key128 key{}; | ||
| 217 | ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); | ||
| 218 | |||
| 219 | // We get the name without the extension in order to create the rights ID. | ||
| 220 | std::string name_only(ticket_file->GetName()); | ||
| 221 | name_only.erase(name_only.size() - 4); | ||
| 222 | |||
| 223 | const auto rights_id_raw = Common::HexStringToArray<16>(name_only); | ||
| 224 | u128 rights_id; | ||
| 225 | std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128)); | ||
| 226 | keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 227 | } | 198 | } |
| 228 | } | 199 | } |
| 229 | 200 | ||
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h index 27f97c725..915bffca9 100644 --- a/src/core/file_sys/submission_package.h +++ b/src/core/file_sys/submission_package.h | |||
| @@ -53,7 +53,6 @@ public: | |||
| 53 | TitleType title_type = TitleType::Application) const; | 53 | TitleType title_type = TitleType::Application) const; |
| 54 | VirtualFile GetNCAFile(u64 title_id, ContentRecordType type, | 54 | VirtualFile GetNCAFile(u64 title_id, ContentRecordType type, |
| 55 | TitleType title_type = TitleType::Application) const; | 55 | TitleType title_type = TitleType::Application) const; |
| 56 | std::vector<Core::Crypto::Key128> GetTitlekey() const; | ||
| 57 | 56 | ||
| 58 | std::vector<VirtualFile> GetFiles() const override; | 57 | std::vector<VirtualFile> GetFiles() const override; |
| 59 | 58 | ||
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 446f46b3c..9eaae4c4b 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp | |||
| @@ -122,20 +122,18 @@ private: | |||
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | void ImportTicket(HLERequestContext& ctx) { | 124 | void ImportTicket(HLERequestContext& ctx) { |
| 125 | const auto ticket = ctx.ReadBuffer(); | 125 | const auto raw_ticket = ctx.ReadBuffer(); |
| 126 | [[maybe_unused]] const auto cert = ctx.ReadBuffer(1); | 126 | [[maybe_unused]] const auto cert = ctx.ReadBuffer(1); |
| 127 | 127 | ||
| 128 | if (ticket.size() < sizeof(Core::Crypto::Ticket)) { | 128 | if (raw_ticket.size() < sizeof(Core::Crypto::Ticket)) { |
| 129 | LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); | 129 | LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); |
| 130 | IPC::ResponseBuilder rb{ctx, 2}; | 130 | IPC::ResponseBuilder rb{ctx, 2}; |
| 131 | rb.Push(ERROR_INVALID_ARGUMENT); | 131 | rb.Push(ERROR_INVALID_ARGUMENT); |
| 132 | return; | 132 | return; |
| 133 | } | 133 | } |
| 134 | 134 | ||
| 135 | Core::Crypto::Ticket raw{}; | 135 | Core::Crypto::Ticket ticket = Core::Crypto::Ticket::Read(raw_ticket); |
| 136 | std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket)); | 136 | if (!keys.AddTicket(ticket)) { |
| 137 | |||
| 138 | if (!keys.AddTicketPersonalized(raw)) { | ||
| 139 | LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); | 137 | LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); |
| 140 | IPC::ResponseBuilder rb{ctx, 2}; | 138 | IPC::ResponseBuilder rb{ctx, 2}; |
| 141 | rb.Push(ERROR_INVALID_ARGUMENT); | 139 | rb.Push(ERROR_INVALID_ARGUMENT); |
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 46125d473..6b966f20d 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp | |||
| @@ -21,7 +21,7 @@ constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; | |||
| 21 | constexpr std::size_t BaseMiiCount{2}; | 21 | constexpr std::size_t BaseMiiCount{2}; |
| 22 | constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; | 22 | constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; |
| 23 | 23 | ||
| 24 | constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; | 24 | constexpr MiiStoreData::Name DefaultMiiName{u'n', u'o', u' ', u'n', u'a', u'm', u'e'}; |
| 25 | constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7}; | 25 | constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7}; |
| 26 | constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13}; | 26 | constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13}; |
| 27 | constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23}; | 27 | constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23}; |
diff --git a/src/core/hle/service/mii/raw_data.cpp b/src/core/hle/service/mii/raw_data.cpp index 1442280c8..80369cdb0 100644 --- a/src/core/hle/service/mii/raw_data.cpp +++ b/src/core/hle/service/mii/raw_data.cpp | |||
| @@ -5,109 +5,7 @@ | |||
| 5 | 5 | ||
| 6 | namespace Service::Mii::RawData { | 6 | namespace Service::Mii::RawData { |
| 7 | 7 | ||
| 8 | const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ | 8 | const std::array<Service::Mii::DefaultMii, 6> DefaultMii{ |
| 9 | Service::Mii::DefaultMii{ | ||
| 10 | .face_type = 0, | ||
| 11 | .face_color = 0, | ||
| 12 | .face_wrinkle = 0, | ||
| 13 | .face_makeup = 0, | ||
| 14 | .hair_type = 33, | ||
| 15 | .hair_color = 1, | ||
| 16 | .hair_flip = 0, | ||
| 17 | .eye_type = 2, | ||
| 18 | .eye_color = 0, | ||
| 19 | .eye_scale = 4, | ||
| 20 | .eye_aspect = 3, | ||
| 21 | .eye_rotate = 4, | ||
| 22 | .eye_x = 2, | ||
| 23 | .eye_y = 12, | ||
| 24 | .eyebrow_type = 6, | ||
| 25 | .eyebrow_color = 1, | ||
| 26 | .eyebrow_scale = 4, | ||
| 27 | .eyebrow_aspect = 3, | ||
| 28 | .eyebrow_rotate = 6, | ||
| 29 | .eyebrow_x = 2, | ||
| 30 | .eyebrow_y = 10, | ||
| 31 | .nose_type = 1, | ||
| 32 | .nose_scale = 4, | ||
| 33 | .nose_y = 9, | ||
| 34 | .mouth_type = 23, | ||
| 35 | .mouth_color = 0, | ||
| 36 | .mouth_scale = 4, | ||
| 37 | .mouth_aspect = 3, | ||
| 38 | .mouth_y = 13, | ||
| 39 | .mustache_type = 0, | ||
| 40 | .beard_type = 0, | ||
| 41 | .beard_color = 0, | ||
| 42 | .mustache_scale = 4, | ||
| 43 | .mustache_y = 10, | ||
| 44 | .glasses_type = 0, | ||
| 45 | .glasses_color = 0, | ||
| 46 | .glasses_scale = 4, | ||
| 47 | .glasses_y = 10, | ||
| 48 | .mole_type = 0, | ||
| 49 | .mole_scale = 4, | ||
| 50 | .mole_x = 2, | ||
| 51 | .mole_y = 20, | ||
| 52 | .height = 64, | ||
| 53 | .weight = 64, | ||
| 54 | .gender = Gender::Male, | ||
| 55 | .favorite_color = 0, | ||
| 56 | .region = 0, | ||
| 57 | .font_region = FontRegion::Standard, | ||
| 58 | .type = 0, | ||
| 59 | }, | ||
| 60 | Service::Mii::DefaultMii{ | ||
| 61 | .face_type = 0, | ||
| 62 | .face_color = 0, | ||
| 63 | .face_wrinkle = 0, | ||
| 64 | .face_makeup = 0, | ||
| 65 | .hair_type = 12, | ||
| 66 | .hair_color = 1, | ||
| 67 | .hair_flip = 0, | ||
| 68 | .eye_type = 4, | ||
| 69 | .eye_color = 0, | ||
| 70 | .eye_scale = 4, | ||
| 71 | .eye_aspect = 3, | ||
| 72 | .eye_rotate = 3, | ||
| 73 | .eye_x = 2, | ||
| 74 | .eye_y = 12, | ||
| 75 | .eyebrow_type = 0, | ||
| 76 | .eyebrow_color = 1, | ||
| 77 | .eyebrow_scale = 4, | ||
| 78 | .eyebrow_aspect = 3, | ||
| 79 | .eyebrow_rotate = 6, | ||
| 80 | .eyebrow_x = 2, | ||
| 81 | .eyebrow_y = 10, | ||
| 82 | .nose_type = 1, | ||
| 83 | .nose_scale = 4, | ||
| 84 | .nose_y = 9, | ||
| 85 | .mouth_type = 23, | ||
| 86 | .mouth_color = 0, | ||
| 87 | .mouth_scale = 4, | ||
| 88 | .mouth_aspect = 3, | ||
| 89 | .mouth_y = 13, | ||
| 90 | .mustache_type = 0, | ||
| 91 | .beard_type = 0, | ||
| 92 | .beard_color = 0, | ||
| 93 | .mustache_scale = 4, | ||
| 94 | .mustache_y = 10, | ||
| 95 | .glasses_type = 0, | ||
| 96 | .glasses_color = 0, | ||
| 97 | .glasses_scale = 4, | ||
| 98 | .glasses_y = 10, | ||
| 99 | .mole_type = 0, | ||
| 100 | .mole_scale = 4, | ||
| 101 | .mole_x = 2, | ||
| 102 | .mole_y = 20, | ||
| 103 | .height = 64, | ||
| 104 | .weight = 64, | ||
| 105 | .gender = Gender::Female, | ||
| 106 | .favorite_color = 0, | ||
| 107 | .region = 0, | ||
| 108 | .font_region = FontRegion::Standard, | ||
| 109 | .type = 0, | ||
| 110 | }, | ||
| 111 | Service::Mii::DefaultMii{ | 9 | Service::Mii::DefaultMii{ |
| 112 | .face_type = 0, | 10 | .face_type = 0, |
| 113 | .face_color = 4, | 11 | .face_color = 4, |
diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h index c2bec68d4..5b81b013b 100644 --- a/src/core/hle/service/mii/raw_data.h +++ b/src/core/hle/service/mii/raw_data.h | |||
| @@ -9,7 +9,7 @@ | |||
| 9 | 9 | ||
| 10 | namespace Service::Mii::RawData { | 10 | namespace Service::Mii::RawData { |
| 11 | 11 | ||
| 12 | extern const std::array<Service::Mii::DefaultMii, 8> DefaultMii; | 12 | extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii; |
| 13 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline; | 13 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline; |
| 14 | extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor; | 14 | extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor; |
| 15 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle; | 15 | extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle; |