From 79cc2793a5b5a18f2c33ebe98ef183e4d7cb72e3 Mon Sep 17 00:00:00 2001 From: pineappleEA Date: Wed, 15 Mar 2023 22:32:40 +0100 Subject: [PATCH] early-access version 3458 --- README.md | 2 +- src/core/hle/service/mii/mii_manager.cpp | 35 ++++++--- src/core/hle/service/mii/mii_manager.h | 7 +- src/core/hle/service/mii/types.h | 60 +++++++++++++++- src/core/hle/service/nfp/amiibo_crypto.cpp | 6 +- src/core/hle/service/nfp/nfp_device.cpp | 71 +++++++++++++------ src/core/hle/service/nfp/nfp_device.h | 1 + src/core/hle/service/nfp/nfp_types.h | 10 +-- .../vulkan_common/vulkan_device.cpp | 6 ++ 9 files changed, 160 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index b53ff5d87..ac8218e44 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ yuzu emulator early access ============= -This is the source code for early-access 3457. +This is the source code for early-access 3458. ## Legal Notice diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index aa98d10b4..0f6752bde 100755 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp @@ -510,7 +510,7 @@ CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const { return mii; } -Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const { +Ver3StoreData MiiManager::BuildFromStoreData(const CharInfo& mii) const { Service::Mii::MiiManager manager; Ver3StoreData mii_v3{}; @@ -534,16 +534,13 @@ Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const { mii_v3.region_information.character_set.Assign(mii.font_region); mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type); - mii_v3.appearance_bits1.skin_color.Assign(mii.faceline_color); mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle); mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make); mii_v3.hair_style = mii.hair_type; - mii_v3.appearance_bits3.hair_color.Assign(mii.hair_color); mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip); mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type); - mii_v3.appearance_bits4.eye_color.Assign(mii.eye_color); mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale); mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect); mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate); @@ -551,7 +548,6 @@ Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const { mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y); mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type); - mii_v3.appearance_bits5.eyebrow_color.Assign(mii.eyebrow_color); mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale); mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect); mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate); @@ -563,7 +559,6 @@ Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const { mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y); mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type); - mii_v3.appearance_bits7.mouth_color.Assign(mii.mouth_color); mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale); mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect); mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y); @@ -573,10 +568,7 @@ Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const { mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y); mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type); - mii_v3.appearance_bits9.facial_hair_color.Assign(mii.beard_color); - mii_v3.appearance_bits10.glasses_type.Assign(mii.glasses_type); - mii_v3.appearance_bits10.glasses_color.Assign(mii.glasses_color); mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale); mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y); @@ -585,11 +577,36 @@ Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const { mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x); mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y); + // These types are converted to V3 from a table + mii_v3.appearance_bits1.skin_color.Assign(Ver3FacelineColorTable[mii.faceline_color]); + mii_v3.appearance_bits3.hair_color.Assign(Ver3HairColorTable[mii.hair_color]); + mii_v3.appearance_bits4.eye_color.Assign(Ver3EyeColorTable[mii.eye_color]); + mii_v3.appearance_bits5.eyebrow_color.Assign(Ver3HairColorTable[mii.eyebrow_color]); + mii_v3.appearance_bits7.mouth_color.Assign(Ver3MouthlineColorTable[mii.mouth_color]); + mii_v3.appearance_bits9.facial_hair_color.Assign(Ver3HairColorTable[mii.beard_color]); + mii_v3.appearance_bits10.glasses_color.Assign(Ver3GlassColorTable[mii.glasses_color]); + mii_v3.appearance_bits10.glasses_type.Assign(Ver3GlassTypeTable[mii.glasses_type]); + + mii_v3.crc = GenerateCrc16(&mii_v3, sizeof(Ver3StoreData) - sizeof(u16)); + // TODO: Validate mii_v3 data return mii_v3; } +NfpStoreDataExtension MiiManager::SetFromStoreData(const CharInfo& mii) const { + return { + .faceline_color = static_cast(mii.faceline_color & 0xf), + .hair_color = static_cast(mii.hair_color & 0x7f), + .eye_color = static_cast(mii.eyebrow_color & 0x7f), + .eyebrow_color = static_cast(mii.eyebrow_color & 0x7f), + .mouth_color = static_cast(mii.mouth_color & 0x7f), + .beard_color = static_cast(mii.beard_color & 0x7f), + .glass_color = static_cast(mii.glasses_color & 0x7f), + .glass_type = static_cast(mii.glasses_type & 0x1f), + }; +} + bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const { bool is_valid = mii_v3.version == 0 || mii_v3.version == 3; diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h index 9a234a56d..cc3848085 100755 --- a/src/core/hle/service/mii/mii_manager.h +++ b/src/core/hle/service/mii/mii_manager.h @@ -23,11 +23,16 @@ public: CharInfo BuildRandom(Age age, Gender gender, Race race); CharInfo BuildDefault(std::size_t index); CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const; - Ver3StoreData ConvertCharInfoToV3(const CharInfo& mii) const; bool ValidateV3Info(const Ver3StoreData& mii_v3) const; ResultVal> GetDefault(SourceFlag source_flag); Result GetIndex(const CharInfo& info, u32& index); + // This is nn::mii::detail::Ver::StoreDataRaw::BuildFromStoreData + Ver3StoreData BuildFromStoreData(const CharInfo& mii) const; + + // This is nn::mii::detail::NfpStoreDataExtentionRaw::SetFromStoreData + NfpStoreDataExtension SetFromStoreData(const CharInfo& mii) const; + private: const Common::UUID user_id{}; u64 update_counter{}; diff --git a/src/core/hle/service/mii/types.h b/src/core/hle/service/mii/types.h index 40a872db3..c643646d0 100755 --- a/src/core/hle/service/mii/types.h +++ b/src/core/hle/service/mii/types.h @@ -365,10 +365,68 @@ struct Ver3StoreData { } appearance_bits11; std::array author_name; - INSERT_PADDING_BYTES(0x4); + INSERT_PADDING_BYTES(0x2); + u16_be crc; }; static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size"); +struct NfpStoreDataExtension { + u8 faceline_color; + u8 hair_color; + u8 eye_color; + u8 eyebrow_color; + u8 mouth_color; + u8 beard_color; + u8 glass_color; + u8 glass_type; +}; +static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size"); + +constexpr std::array Ver3FacelineColorTable{ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5, +}; + +constexpr std::array Ver3HairColorTable{ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0, + 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7, + 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4, +}; + +constexpr std::array Ver3EyeColorTable{ + 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4, + 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, + 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, + 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, +}; + +constexpr std::array Ver3MouthlineColorTable{ + 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, + 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, + 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, + 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, + 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, + 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3, +}; + +constexpr std::array Ver3GlassColorTable{ + 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3, + 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, + 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5, + 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, +}; + +constexpr std::array Ver3GlassTypeTable{ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1, + 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7, +}; + struct MiiStoreData { using Name = std::array; diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp index 50b6e0b54..7b0b2938d 100755 --- a/src/core/hle/service/nfp/amiibo_crypto.cpp +++ b/src/core/hle/service/nfp/amiibo_crypto.cpp @@ -88,8 +88,9 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { encoded_data.application_area_id = nfc_data.user_memory.application_area_id; encoded_data.application_id_byte = nfc_data.user_memory.application_id_byte; encoded_data.unknown = nfc_data.user_memory.unknown; + encoded_data.mii_extension = nfc_data.user_memory.mii_extension; encoded_data.unknown2 = nfc_data.user_memory.unknown2; - encoded_data.application_area_crc = nfc_data.user_memory.application_area_crc; + encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc; encoded_data.application_area = nfc_data.user_memory.application_area; encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag; encoded_data.lock_bytes = nfc_data.uuid.lock_bytes; @@ -122,8 +123,9 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { nfc_data.user_memory.application_area_id = encoded_data.application_area_id; nfc_data.user_memory.application_id_byte = encoded_data.application_id_byte; nfc_data.user_memory.unknown = encoded_data.unknown; + nfc_data.user_memory.mii_extension = encoded_data.mii_extension; nfc_data.user_memory.unknown2 = encoded_data.unknown2; - nfc_data.user_memory.application_area_crc = encoded_data.application_area_crc; + nfc_data.user_memory.register_info_crc = encoded_data.register_info_crc; nfc_data.user_memory.application_area = encoded_data.application_area; nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag; nfc_data.user_memory.model_info = encoded_data.model_info; diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp index 79f9d20ad..4ddc8f0c1 100755 --- a/src/core/hle/service/nfp/nfp_device.cpp +++ b/src/core/hle/service/nfp/nfp_device.cpp @@ -448,7 +448,7 @@ Result NfpDevice::DeleteRegisterInfo() { rng.GenerateRandomBytes(&tag_data.unknown, sizeof(u8)); rng.GenerateRandomBytes(&tag_data.unknown2[0], sizeof(u32)); rng.GenerateRandomBytes(&tag_data.unknown2[1], sizeof(u32)); - rng.GenerateRandomBytes(&tag_data.application_area_crc, sizeof(u32)); + rng.GenerateRandomBytes(&tag_data.register_info_crc, sizeof(u32)); rng.GenerateRandomBytes(&tag_data.settings.init_date, sizeof(u32)); tag_data.settings.settings.font_region.Assign(0); tag_data.settings.settings.amiibo_initialized.Assign(0); @@ -471,6 +471,7 @@ Result NfpDevice::SetRegisterInfoPrivate(const AmiiboName& amiibo_name) { } Service::Mii::MiiManager manager; + const auto mii = manager.BuildDefault(0); auto& settings = tag_data.settings; if (tag_data.settings.settings.amiibo_initialized == 0) { @@ -479,16 +480,15 @@ Result NfpDevice::SetRegisterInfoPrivate(const AmiiboName& amiibo_name) { } SetAmiiboName(settings, amiibo_name); - tag_data.owner_mii = manager.ConvertCharInfoToV3(manager.BuildDefault(0)); + tag_data.owner_mii = manager.BuildFromStoreData(mii); tag_data.unknown = 0; - tag_data.unknown2[6] = 0; + tag_data.mii_extension = manager.SetFromStoreData(mii); + tag_data.unknown2[4] = 0; settings.country_code_id = 0; settings.settings.font_region.Assign(0); settings.settings.amiibo_initialized.Assign(1); - // TODO: this is a mix of tag.file input - std::array unknown_input{}; - tag_data.application_area_crc = CalculateCrc(unknown_input); + UpdateRegisterInfoCrc(); return Flush(); } @@ -685,6 +685,11 @@ Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span dat return WrongDeviceState; } + if (is_app_area_open) { + LOG_ERROR(Service_NFP, "Application area is open"); + return WrongDeviceState; + } + if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); return WrongDeviceState; @@ -715,10 +720,9 @@ Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span dat tag_data.settings.settings.appdata_initialized.Assign(1); tag_data.application_area_id = access_id; tag_data.unknown = {}; + tag_data.unknown2 = {}; - // TODO: this is a mix of tag_data input - std::array unknown_input{}; - tag_data.application_area_crc = CalculateCrc(unknown_input); + UpdateRegisterInfoCrc(); return Flush(); } @@ -752,6 +756,10 @@ Result NfpDevice::DeleteApplicationArea() { rng.GenerateRandomBytes(&tag_data.application_id_byte, sizeof(u8)); tag_data.settings.settings.appdata_initialized.Assign(0); tag_data.unknown = {}; + tag_data.unknown2 = {}; + is_app_area_open = false; + + UpdateRegisterInfoCrc(); return Flush(); } @@ -838,6 +846,31 @@ void NfpDevice::UpdateSettingsCrc() { settings.crc = CalculateCrc(unknown_input); } +void NfpDevice::UpdateRegisterInfoCrc() { +#pragma pack(push, 1) + struct CrcData { + Mii::Ver3StoreData mii; + u8 application_id_byte; + u8 unknown; + Mii::NfpStoreDataExtension mii_extension; + std::array unknown2; + }; + static_assert(sizeof(CrcData) == 0x7e, "CrcData is an invalid size"); +#pragma pack(pop) + + const CrcData crc_data{ + .mii = tag_data.owner_mii, + .application_id_byte = tag_data.application_id_byte, + .unknown = tag_data.unknown, + .mii_extension = tag_data.mii_extension, + .unknown2 = tag_data.unknown2, + }; + + std::array data{}; + memcpy(data.data(), &crc_data, sizeof(CrcData)); + tag_data.register_info_crc = CalculateCrc(data); +} + u32 NfpDevice::CalculateCrc(std::span data) { constexpr u32 magic = 0xedb88320; u32 crc = 0xffffffff; @@ -847,17 +880,15 @@ u32 NfpDevice::CalculateCrc(std::span data) { } for (u8 input : data) { - u32 temp = (crc ^ input) >> 1; - if (((crc ^ input) & 1) != 0) { - temp = temp ^ magic; - } - - for (std::size_t step = 0; step < 7; ++step) { - crc = temp >> 1; - if ((temp & 1) != 0) { - crc = temp >> 1 ^ magic; - } - } + crc ^= input; + crc = crc >> 1 ^ ((crc & 1) ? magic : 0x0); + crc = crc >> 1 ^ ((crc & 1) ? magic : 0x0); + crc = crc >> 1 ^ ((crc & 1) ? magic : 0x0); + crc = crc >> 1 ^ ((crc & 1) ? magic : 0x0); + crc = crc >> 1 ^ ((crc & 1) ? magic : 0x0); + crc = crc >> 1 ^ ((crc & 1) ? magic : 0x0); + crc = crc >> 1 ^ ((crc & 1) ? magic : 0x0); + crc = crc >> 1 ^ ((crc & 1) ? magic : 0x0); } return ~crc; diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h index a46ed469d..9f1fe3c8a 100755 --- a/src/core/hle/service/nfp/nfp_device.h +++ b/src/core/hle/service/nfp/nfp_device.h @@ -80,6 +80,7 @@ private: AmiiboDate GetAmiiboDate(s64 posix_time) const; u64 RemoveVersionByte(u64 application_id) const; void UpdateSettingsCrc(); + void UpdateRegisterInfoCrc(); u32 CalculateCrc(std::span); bool is_controller_set{}; diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h index 7b1808fbf..4b8adb45f 100755 --- a/src/core/hle/service/nfp/nfp_types.h +++ b/src/core/hle/service/nfp/nfp_types.h @@ -259,8 +259,9 @@ struct EncryptedAmiiboFile { u32_be application_area_id; // Encrypted Game id u8 application_id_byte; u8 unknown; - std::array unknown2; - u32_be application_area_crc; + Service::Mii::NfpStoreDataExtension mii_extension; + std::array unknown2; + u32_be register_info_crc; ApplicationArea application_area; // Encrypted Game data }; static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); @@ -280,8 +281,9 @@ struct NTAG215File { u32_be application_area_id; u8 application_id_byte; u8 unknown; - std::array unknown2; - u32_be application_area_crc; + Service::Mii::NfpStoreDataExtension mii_extension; + std::array unknown2; + u32_be register_info_crc; ApplicationArea application_area; // Encrypted Game data HashData hmac_tag; // Hash UniqueSerialNumber uid; // Unique serial number diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index ea0b063a2..6c4caafef 100755 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -401,6 +401,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); } } + if (extensions.extended_dynamic_state3 && is_radv) { + LOG_WARNING(Render_Vulkan, "RADV has broken extendedDynamicState3ColorBlendEquation"); + features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false; + features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = false; + dynamic_state3_blending = false; + } if (extensions.vertex_input_dynamic_state && is_radv) { // TODO(ameerj): Blacklist only offending driver versions // TODO(ameerj): Confirm if RDNA1 is affected