diff --git a/Source/Core/DiscIO/Src/Volume.h b/Source/Core/DiscIO/Src/Volume.h index ec20e47b6c..f8a6f13ecb 100644 --- a/Source/Core/DiscIO/Src/Volume.h +++ b/Source/Core/DiscIO/Src/Volume.h @@ -38,9 +38,11 @@ public: virtual std::string GetUniqueID() const = 0; virtual std::string GetMakerID() const = 0; virtual std::string GetName() const = 0; - virtual bool GetWName(std::vector& _rwNames) const {return false;}; + virtual bool GetWName(std::vector& _rwNames) const { return false; } virtual u32 GetFSTSize() const = 0; virtual std::string GetApploaderDate() const = 0; + virtual bool SupportsIntegrityCheck() const { return false; } + virtual bool CheckIntegrity() const { return false; } enum ECountry { diff --git a/Source/Core/DiscIO/Src/VolumeWiiCrypted.cpp b/Source/Core/DiscIO/Src/VolumeWiiCrypted.cpp index 9e7865d9c6..ca010b5ae4 100644 --- a/Source/Core/DiscIO/Src/VolumeWiiCrypted.cpp +++ b/Source/Core/DiscIO/Src/VolumeWiiCrypted.cpp @@ -17,6 +17,7 @@ #include "VolumeWiiCrypted.h" #include "StringUtil.h" +#include "Crypto/sha1.h" namespace DiscIO { @@ -232,4 +233,68 @@ u64 CVolumeWiiCrypted::GetSize() const } } +bool CVolumeWiiCrypted::CheckIntegrity() const +{ + // Get partition data size + u32 partSizeDiv4; + RAWRead(m_VolumeOffset + 0x2BC, 4, (u8*)&partSizeDiv4); + u64 partDataSize = (u64)Common::swap32(partSizeDiv4) * 4; + + u32 nClusters = (u32)(partDataSize / 0x8000); + for (u32 clusterID = 0; clusterID < nClusters; ++clusterID) + { + u64 clusterOff = m_VolumeOffset + dataOffset + (u64)clusterID * 0x8000; + + // Read and decrypt the cluster metadata + u8 clusterMDCrypted[0x400]; + u8 clusterMD[0x400]; + u8 IV[16] = { 0 }; + if (!m_pReader->Read(clusterOff, 0x400, clusterMDCrypted)) + { + NOTICE_LOG(DISCIO, "Integrity Check: fail at cluster %d: could not read metadata", clusterID); + return false; + } + AES_cbc_encrypt(clusterMDCrypted, clusterMD, 0x400, &m_AES_KEY, IV, AES_DECRYPT); + + // Some clusters have invalid data and metadata because they aren't + // meant to be read by the game (for example, holes between files). To + // try to avoid reporting errors because of these clusters, we check + // the 0x00 paddings in the metadata. + // + // This may cause some false negatives though: some bad clusters may be + // skipped because they are *too* bad and are not even recognized as + // valid clusters. To be improved. + bool meaningless = false; + for (u32 idx = 0x26C; idx < 0x280; ++idx) + if (clusterMD[idx] != 0) + meaningless = true; + + if (meaningless) + continue; + + u8 clusterData[0x7C00]; + if (!Read((u64)clusterID * 0x7C00, 0x7C00, clusterData)) + { + NOTICE_LOG(DISCIO, "Integrity Check: fail at cluster %d: could not read data", clusterID); + return false; + } + + for (u32 hashID = 0; hashID < 31; ++hashID) + { + u8 hash[20]; + + sha1(clusterData + hashID * 0x400, 0x400, hash); + + // Note that we do not use strncmp here + if (memcmp(hash, clusterMD + hashID * 20, 20)) + { + NOTICE_LOG(DISCIO, "Integrity Check: fail at cluster %d: hash %d is invalid", clusterID, hashID); + return false; + } + } + } + + return true; +} + } // namespace diff --git a/Source/Core/DiscIO/Src/VolumeWiiCrypted.h b/Source/Core/DiscIO/Src/VolumeWiiCrypted.h index f69bcfb490..5010e46bbd 100644 --- a/Source/Core/DiscIO/Src/VolumeWiiCrypted.h +++ b/Source/Core/DiscIO/Src/VolumeWiiCrypted.h @@ -43,6 +43,9 @@ public: ECountry GetCountry() const; u64 GetSize() const; + bool SupportsIntegrityCheck() const { return true; } + bool CheckIntegrity() const; + private: IBlobReader* m_pReader; diff --git a/Source/Core/DolphinWX/Src/ISOProperties.cpp b/Source/Core/DolphinWX/Src/ISOProperties.cpp index 258a71e621..f004989055 100644 --- a/Source/Core/DolphinWX/Src/ISOProperties.cpp +++ b/Source/Core/DolphinWX/Src/ISOProperties.cpp @@ -75,6 +75,7 @@ BEGIN_EVENT_TABLE(CISOProperties, wxDialog) EVT_MENU(IDM_EXTRACTALL, CISOProperties::OnExtractDir) EVT_MENU(IDM_EXTRACTAPPLOADER, CISOProperties::OnExtractDataFromHeader) EVT_MENU(IDM_EXTRACTDOL, CISOProperties::OnExtractDataFromHeader) + EVT_MENU(IDM_CHECKINTEGRITY, CISOProperties::CheckPartitionIntegrity) EVT_CHOICE(ID_LANG, CISOProperties::OnChangeBannerLang) END_EVENT_TABLE() @@ -635,6 +636,13 @@ void CISOProperties::OnRightClickOnTree(wxTreeEvent& event) popupMenu->Append(IDM_EXTRACTAPPLOADER, _("Extract Apploader...")); popupMenu->Append(IDM_EXTRACTDOL, _("Extract DOL...")); + if (m_Treectrl->GetItemImage(m_Treectrl->GetSelection()) == 0 + && m_Treectrl->GetFirstVisibleItem() != m_Treectrl->GetSelection()) + { + popupMenu->AppendSeparator(); + popupMenu->Append(IDM_CHECKINTEGRITY, _("Check Partition Integrity")); + } + PopupMenu(popupMenu); event.Skip(); @@ -840,6 +848,71 @@ void CISOProperties::OnExtractDataFromHeader(wxCommandEvent& event) PanicAlertT("Failed to extract to %s!", (const char *)Path.mb_str()); } +class IntegrityCheckThread : public wxThread +{ +public: + IntegrityCheckThread(const WiiPartition& Partition) + : wxThread(wxTHREAD_JOINABLE), m_Partition(Partition) + { + Create(); + } + + virtual ExitCode Entry() + { + return (ExitCode)m_Partition.Partition->CheckIntegrity(); + } + +private: + const WiiPartition& m_Partition; +}; + +void CISOProperties::CheckPartitionIntegrity(wxCommandEvent& event) +{ + // Normally we can't enter this function if we aren't analyzing a Wii disc + // anyway, but let's still check to be sure. + if (!DiscIO::IsVolumeWiiDisc(OpenISO)) + return; + + wxString PartitionName = m_Treectrl->GetItemText(m_Treectrl->GetSelection()); + if (!PartitionName) + return; + + // Get the partition number from the item text ("Partition N") + int PartitionNum = wxAtoi(PartitionName.SubString(10, 11)); + const WiiPartition& Partition = WiiDisc[PartitionNum]; + + wxProgressDialog* dialog = new wxProgressDialog( + _("Checking integrity..."), _("Working..."), 1000, this, + wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_SMOOTH + ); + + IntegrityCheckThread thread(Partition); + thread.Run(); + + while (thread.IsAlive()) + { + dialog->Pulse(); + wxThread::Sleep(50); + } + + delete dialog; + + if (!thread.Wait()) + { + wxMessageBox( + wxString::Format(_("Integrity check for partition %d failed. " + "Your dump is most likely corrupted or has been " + "patched incorrectly."), PartitionNum), + _("Integrity Check Error"), wxOK | wxICON_ERROR, this + ); + } + else + { + wxMessageBox(_("Integrity check completed. No errors have been found."), + _("Integrity check completed"), wxOK | wxICON_INFORMATION, this); + } +} + void CISOProperties::SetRefresh(wxCommandEvent& event) { bRefreshList = true; diff --git a/Source/Core/DolphinWX/Src/ISOProperties.h b/Source/Core/DolphinWX/Src/ISOProperties.h index 6e72993b42..da275136fe 100644 --- a/Source/Core/DolphinWX/Src/ISOProperties.h +++ b/Source/Core/DolphinWX/Src/ISOProperties.h @@ -170,6 +170,7 @@ private: IDM_EXTRACTFILE, IDM_EXTRACTAPPLOADER, IDM_EXTRACTDOL, + IDM_CHECKINTEGRITY, IDM_BNRSAVEAS }; @@ -186,6 +187,7 @@ private: void OnExtractFile(wxCommandEvent& event); void OnExtractDir(wxCommandEvent& event); void OnExtractDataFromHeader(wxCommandEvent& event); + void CheckPartitionIntegrity(wxCommandEvent& event); void SetRefresh(wxCommandEvent& event); void OnChangeBannerLang(wxCommandEvent& event); void PHackButtonClicked(wxCommandEvent& event);