diff --git a/Core/FileSystems/BlockDevices.cpp b/Core/FileSystems/BlockDevices.cpp index 9d4f85ee6ad8..5314fac12547 100644 --- a/Core/FileSystems/BlockDevices.cpp +++ b/Core/FileSystems/BlockDevices.cpp @@ -50,11 +50,13 @@ BlockDevice *constructBlockDevice(FileLoader *fileLoader) { return new FileBlockDevice(fileLoader); } -u32 BlockDevice::CalculateCRC() { +u32 BlockDevice::CalculateCRC(volatile bool *cancel) { u32 crc = crc32(0, Z_NULL, 0); u8 block[2048]; for (u32 i = 0; i < GetNumBlocks(); ++i) { + if (cancel && *cancel) + return 0; if (!ReadBlock(i, block, true)) { ERROR_LOG(FILESYS, "Failed to read block for CRC"); return 0; diff --git a/Core/FileSystems/BlockDevices.h b/Core/FileSystems/BlockDevices.h index 2db068c77716..3268b27fa7fe 100644 --- a/Core/FileSystems/BlockDevices.h +++ b/Core/FileSystems/BlockDevices.h @@ -47,7 +47,7 @@ class BlockDevice { virtual u32 GetNumBlocks() = 0; virtual bool IsDisc() = 0; - u32 CalculateCRC(); + u32 CalculateCRC(volatile bool *cancel = nullptr); void NotifyReadError(); protected: diff --git a/Core/Reporting.cpp b/Core/Reporting.cpp index df0e50e043df..3a621a082545 100644 --- a/Core/Reporting.cpp +++ b/Core/Reporting.cpp @@ -102,6 +102,8 @@ namespace Reporting static std::condition_variable crcCond; static std::string crcFilename; static std::map crcResults; + static volatile bool crcPending = false; + static volatile bool crcCancel = false; static std::thread crcThread; static int CalculateCRCThread() { @@ -113,7 +115,7 @@ namespace Reporting u32 crc = 0; if (blockDevice) { - crc = blockDevice->CalculateCRC(); + crc = blockDevice->CalculateCRC(&crcCancel); } delete blockDevice; @@ -121,6 +123,7 @@ namespace Reporting std::lock_guard guard(crcLock); crcResults[crcFilename] = crc; + crcPending = false; crcCond.notify_one(); return 0; @@ -136,18 +139,18 @@ namespace Reporting return; } - if (crcFilename == gamePath) { + if (crcPending) { // Already in process. return; } crcFilename = gamePath; + crcPending = true; + crcCancel = false; crcThread = std::thread(CalculateCRCThread); } bool HasCRC(const std::string &gamePath) { - QueueCRC(gamePath); - std::lock_guard guard(crcLock); return crcResults.find(gamePath) != crcResults.end(); } @@ -167,6 +170,26 @@ namespace Reporting return it->second; } + static uint32_t RetrieveCRCUnlessPowerSaving(const std::string &gamePath) { + // It's okay to use it if we have it already. + if (Core_GetPowerSaving() && !HasCRC(gamePath)) { + return 0; + } + + return RetrieveCRC(gamePath); + } + + static void PurgeCRC() { + std::unique_lock guard(crcLock); + crcCancel = true; + while (crcPending) { + crcCond.wait(guard); + } + + if (crcThread.joinable()) + crcThread.join(); + } + // Returns the full host (e.g. report.ppsspp.org:80.) std::string ServerHost() { @@ -325,6 +348,7 @@ namespace Reporting compatThread.join(); if (messageThread.joinable()) messageThread.join(); + PurgeCRC(); // Just so it can be enabled in the menu again. Init(); @@ -472,7 +496,7 @@ namespace Reporting postdata.Add("graphics", StringFromFormat("%d", payload.int1)); postdata.Add("speed", StringFromFormat("%d", payload.int2)); postdata.Add("gameplay", StringFromFormat("%d", payload.int3)); - postdata.Add("crc", StringFromFormat("%08x", Core_GetPowerSaving() ? 0 : RetrieveCRC(PSP_CoreParameter().fileToStart))); + postdata.Add("crc", StringFromFormat("%08x", RetrieveCRCUnlessPowerSaving(PSP_CoreParameter().fileToStart))); postdata.Add("suggestions", payload.string1 != "perfect" && payload.string1 != "playable" ? "1" : "0"); AddScreenshotData(postdata, payload.string2); payload.string1.clear(); diff --git a/Core/Reporting.h b/Core/Reporting.h index 87a6d186a45c..3aa20e4eb931 100644 --- a/Core/Reporting.h +++ b/Core/Reporting.h @@ -87,7 +87,10 @@ namespace Reporting // Get the latest compatibility result. Only valid when GetStatus() is not BUSY. std::vector CompatibilitySuggestions(); - // Queues game for CRC hash if needed, and returns true if the hash is available. + // Queues game for CRC hash if needed. + void QueueCRC(const std::string &gamePath); + + // Returns true if the hash is available, does not queue if not. bool HasCRC(const std::string &gamePath); // Blocks until the CRC hash is available for game, and returns it. diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 25e6971e40ed..9677c9373e9b 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -1657,8 +1657,11 @@ void FramebufferManagerCommon::ApplyClearToMemory(int x1, int y1, int x2, int y2 return; } } + if (!Memory::IsValidAddress(gstate.getFrameBufAddress())) { + return; + } - u8 *addr = Memory::GetPointer(gstate.getFrameBufAddress()); + u8 *addr = Memory::GetPointerUnchecked(gstate.getFrameBufAddress()); const int bpp = gstate.FrameBufFormat() == GE_FORMAT_8888 ? 4 : 2; u32 clearBits = clearColor; @@ -1988,6 +1991,8 @@ bool FramebufferManagerCommon::GetFramebuffer(u32 fb_address, int fb_stride, GEB } if (!vfb) { + if (!Memory::IsValidAddress(fb_address)) + return false; // If there's no vfb and we're drawing there, must be memory? buffer = GPUDebugBuffer(Memory::GetPointer(fb_address), fb_stride, 512, format); return true; @@ -2043,6 +2048,8 @@ bool FramebufferManagerCommon::GetDepthbuffer(u32 fb_address, int fb_stride, u32 } if (!vfb) { + if (!Memory::IsValidAddress(z_address)) + return false; // If there's no vfb and we're drawing there, must be memory? buffer = GPUDebugBuffer(Memory::GetPointer(z_address), z_stride, 512, GPU_DBG_FORMAT_16BIT); return true; @@ -2078,6 +2085,8 @@ bool FramebufferManagerCommon::GetStencilbuffer(u32 fb_address, int fb_stride, G } if (!vfb) { + if (!Memory::IsValidAddress(fb_address)) + return false; // If there's no vfb and we're drawing there, must be memory? // TODO: Actually get the stencil. buffer = GPUDebugBuffer(Memory::GetPointer(fb_address), fb_stride, 512, GPU_DBG_FORMAT_8888); diff --git a/GPU/Directx9/FramebufferManagerDX9.cpp b/GPU/Directx9/FramebufferManagerDX9.cpp index 263ef44b63e2..08c40135b324 100644 --- a/GPU/Directx9/FramebufferManagerDX9.cpp +++ b/GPU/Directx9/FramebufferManagerDX9.cpp @@ -464,6 +464,8 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { } if (!vfb) { + if (!Memory::IsValidAddress(fb_address)) + return false; // If there's no vfb and we're drawing there, must be memory? buffer = GPUDebugBuffer(Memory::GetPointer(fb_address), fb_stride, 512, fb_format); return true; diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 697a2145c9dd..b42b80abf4aa 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -27,10 +27,12 @@ #include "Common/Data/Text/I18n.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/File/FileUtil.h" +#include "Common/StringUtils.h" #include "Common/System/System.h" #include "Common/System/NativeApp.h" #include "Core/Host.h" #include "Core/Config.h" +#include "Core/Reporting.h" #include "Core/System.h" #include "UI/CwCheatScreen.h" #include "UI/EmuScreen.h" @@ -92,12 +94,16 @@ void GameScreen::CreateViews() { tvInstallDataSize_->SetVisibility(V_GONE); tvRegion_ = infoLayout->Add(new TextView("", ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); tvRegion_->SetShadow(true); + tvCRC_ = infoLayout->Add(new TextView("", ALIGN_LEFT, true, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); + tvCRC_->SetShadow(true); + tvCRC_->SetVisibility(Reporting::HasCRC(gamePath_) ? V_VISIBLE : V_GONE); } else { tvTitle_ = nullptr; tvGameSize_ = nullptr; tvSaveDataSize_ = nullptr; tvInstallDataSize_ = nullptr; tvRegion_ = nullptr; + tvCRC_ = nullptr; } ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); @@ -234,6 +240,13 @@ void GameScreen::render() { } } + if (tvCRC_ && Reporting::HasCRC(gamePath_)) { + auto rp = GetI18NCategory("Reporting"); + std::string crc = StringFromFormat("%08X", Reporting::RetrieveCRC(gamePath_)); + tvCRC_->SetText(ReplaceAll(rp->T("FeedbackCRCValue", "Disc CRC: [VALUE]"), "[VALUE]", crc)); + tvCRC_->SetVisibility(UI::V_VISIBLE); + } + if (!info->id.empty()) { btnGameSettings_->SetVisibility(info->hasConfig ? UI::V_VISIBLE : UI::V_GONE); btnDeleteGameConfig_->SetVisibility(info->hasConfig ? UI::V_VISIBLE : UI::V_GONE); diff --git a/UI/GameScreen.h b/UI/GameScreen.h index 2e7fa3f5966f..a74625043606 100644 --- a/UI/GameScreen.h +++ b/UI/GameScreen.h @@ -67,6 +67,7 @@ class GameScreen : public UIDialogScreenWithGameBackground { UI::TextView *tvSaveDataSize_; UI::TextView *tvInstallDataSize_; UI::TextView *tvRegion_; + UI::TextView *tvCRC_; UI::Choice *btnGameSettings_; UI::Choice *btnCreateGameConfig_; diff --git a/UI/ReportScreen.cpp b/UI/ReportScreen.cpp index 1c31856a26af..d9bc939df9bc 100644 --- a/UI/ReportScreen.cpp +++ b/UI/ReportScreen.cpp @@ -195,6 +195,7 @@ void ReportScreen::update() { } } UIDialogScreenWithGameBackground::update(); + UpdateCRCInfo(); } void ReportScreen::resized() { @@ -269,7 +270,7 @@ void ReportScreen::CreateViews() { } #ifdef MOBILE_DEVICE - if (!Core_GetPowerSaving()) { + if (!Core_GetPowerSaving() && !Reporting::HasCRC(gamePath_)) { auto crcWarning = new TextView(rp->T("FeedbackIncludeCRC", "Note: Battery will be used to send a disc CRC"), FLAG_WRAP_TEXT, false, new LinearLayoutParams(Margins(12, 5, 0, 5))); crcWarning->SetShadow(true); crcWarning->SetEnabledPtr(&enableReporting_); @@ -277,6 +278,11 @@ void ReportScreen::CreateViews() { } #endif + crcInfo_ = new TextView("", FLAG_WRAP_TEXT, false, new LinearLayoutParams(Margins(12, 5, 0, 5))); + crcInfo_->SetShadow(true); + crcInfo_->SetVisibility(V_GONE); + leftColumnItems->Add(crcInfo_); + if (tookScreenshot_ && !screenshotFilename_.empty()) { leftColumnItems->Add(new CheckBox(&includeScreenshot_, rp->T("FeedbackIncludeScreen", "Include a screenshot")))->SetEnabledPtr(&enableReporting_); screenshot_ = leftColumnItems->Add(new AsyncImageFileView(screenshotFilename_, IS_KEEP_ASPECT, nullptr, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(12, 0)))); @@ -300,6 +306,8 @@ void ReportScreen::CreateViews() { rightColumnItems->SetSpacing(0.0f); rightColumnItems->Add(new Choice(rp->T("Open Browser")))->OnClick.Handle(this, &ReportScreen::HandleBrowser); + showCrcButton_ = new Choice(rp->T("Show disc CRC")); + rightColumnItems->Add(showCrcButton_)->OnClick.Handle(this, &ReportScreen::HandleShowCRC); submit_ = new Choice(rp->T("Submit Feedback")); rightColumnItems->Add(submit_)->OnClick.Handle(this, &ReportScreen::HandleSubmit); UpdateSubmit(); @@ -314,12 +322,32 @@ void ReportScreen::CreateViews() { leftColumn->Add(leftColumnItems); rightColumn->Add(rightColumnItems); + + UpdateCRCInfo(); } void ReportScreen::UpdateSubmit() { submit_->SetEnabled(enableReporting_ && overall_ != ReportingOverallScore::INVALID && graphics_ >= 0 && speed_ >= 0 && gameplay_ >= 0); } +void ReportScreen::UpdateCRCInfo() { + auto rp = GetI18NCategory("Reporting"); + std::string updated; + + if (Reporting::HasCRC(gamePath_)) { + std::string crc = StringFromFormat("%08X", Reporting::RetrieveCRC(gamePath_)); + updated = ReplaceAll(rp->T("FeedbackCRCValue", "Disc CRC: [VALUE]"), "[VALUE]", crc); + } else if (showCRC_) { + updated = rp->T("FeedbackCRCCalculating", "Disc CRC: Calculating..."); + } + + if (!updated.empty()) { + crcInfo_->SetText(updated); + crcInfo_->SetVisibility(V_VISIBLE); + showCrcButton_->SetEnabled(false); + } +} + void ReportScreen::UpdateOverallDescription() { auto rp = GetI18NCategory("Reporting"); const char *desc; @@ -366,6 +394,12 @@ EventReturn ReportScreen::HandleBrowser(EventParams &e) { return EVENT_DONE; } +EventReturn ReportScreen::HandleShowCRC(EventParams &e) { + Reporting::QueueCRC(gamePath_); + showCRC_ = true; + return EVENT_DONE; +} + ReportFinishScreen::ReportFinishScreen(const std::string &gamePath, ReportingOverallScore score) : UIDialogScreenWithGameBackground(gamePath), score_(score) { } diff --git a/UI/ReportScreen.h b/UI/ReportScreen.h index b77f8a082ff9..4a99dd57397d 100644 --- a/UI/ReportScreen.h +++ b/UI/ReportScreen.h @@ -42,17 +42,21 @@ class ReportScreen : public UIDialogScreenWithGameBackground { void resized() override; void CreateViews() override; void UpdateSubmit(); + void UpdateCRCInfo(); void UpdateOverallDescription(); UI::EventReturn HandleChoice(UI::EventParams &e); UI::EventReturn HandleSubmit(UI::EventParams &e); UI::EventReturn HandleBrowser(UI::EventParams &e); + UI::EventReturn HandleShowCRC(UI::EventParams &e); UI::EventReturn HandleReportingChange(UI::EventParams &e); UI::Choice *submit_ = nullptr; UI::View *screenshot_ = nullptr; UI::TextView *reportingNotice_ = nullptr; UI::TextView *overallDescription_ = nullptr; + UI::TextView *crcInfo_ = nullptr; + UI::Choice *showCrcButton_ = nullptr; std::string screenshotFilename_; ReportingOverallScore overall_ = ReportingOverallScore::INVALID; @@ -63,6 +67,7 @@ class ReportScreen : public UIDialogScreenWithGameBackground { bool ratingEnabled_; bool tookScreenshot_ = false; bool includeScreenshot_ = true; + bool showCRC_ = false; }; class ReportFinishScreen : public UIDialogScreenWithGameBackground {