diff --git a/src/dusk/ui/prelaunch.cpp b/src/dusk/ui/prelaunch.cpp
index b091239bc2..02fabde70f 100644
--- a/src/dusk/ui/prelaunch.cpp
+++ b/src/dusk/ui/prelaunch.cpp
@@ -80,16 +80,22 @@ void file_dialog_callback(void*, const char* path, const char* error) {
auto& state = prelaunch_state();
const auto validation = iso::validate(path);
- if (validation != iso::ValidationError::Success) {
- state.errorString = escape(get_error_msg(validation));
+ if (validation == iso::ValidationError::Success) {
+ state.selectedDiscPath = path;
+ state.errorString.clear();
+ state.pendingDiscPath.clear();
+ state.userAcceptedDiscPath.clear();
+ getSettings().backend.isoPath.setValue(state.selectedDiscPath);
+ config::Save();
+ refresh_state();
return;
}
-
- state.selectedDiscPath = path;
- state.errorString.clear();
- getSettings().backend.isoPath.setValue(state.selectedDiscPath);
- config::Save();
- refresh_state();
+ if (validation == iso::ValidationError::HashMismatch) {
+ state.pendingDiscPath = path;
+ } else {
+ state.pendingDiscPath.clear();
+ }
+ state.errorString = escape(get_error_msg(validation));
}
PrelaunchState sPrelaunchState;
@@ -100,13 +106,86 @@ PrelaunchState& prelaunch_state() noexcept {
void refresh_state() noexcept {
auto& state = prelaunch_state();
- const auto validation = iso::validate(state.selectedDiscPath.c_str());
- if (state.selectedDiscPath.empty() || validation != iso::ValidationError::Success) {
+ if (state.selectedDiscPath.empty()) {
state.selectedDiscIsValid = false;
return;
}
- state.selectedDiscIsValid = true;
- state.selectedDiscIsPal = iso::isPal(state.selectedDiscPath.c_str());
+ if (!state.userAcceptedDiscPath.empty() &&
+ state.selectedDiscPath == state.userAcceptedDiscPath) {
+ state.selectedDiscIsValid = true;
+ state.selectedDiscIsPal = iso::isPal(state.selectedDiscPath.c_str());
+ return;
+ }
+
+ const auto validation = iso::validate(state.selectedDiscPath.c_str());
+ // Allow HashMismatch, so that the user is not prompted to accept the warning on every boot.
+ if (validation >= iso::ValidationError::HashMismatch) {
+ state.selectedDiscIsValid = true;
+ state.selectedDiscIsPal = iso::isPal(state.selectedDiscPath.c_str());
+ state.userAcceptedDiscPath.clear();
+ return;
+ }
+ state.selectedDiscIsValid = false;
+}
+
+void try_push_verification_modal(Document& host) {
+ auto& state = prelaunch_state();
+ if (state.errorString.empty()) {
+ return;
+ }
+
+ auto dismiss = [](Modal& modal) {
+ auto& state = prelaunch_state();
+ state.errorString.clear();
+ state.pendingDiscPath.clear();
+ modal.pop();
+ };
+
+ if (!state.pendingDiscPath.empty()) {
+ const Rml::String bodyRml = state.errorString + "
You may proceed at your own risk.";
+ auto acceptHashMismatch = [](Modal& modal) {
+ auto& st = prelaunch_state();
+ std::string path = std::move(st.pendingDiscPath);
+ st.pendingDiscPath.clear();
+ st.errorString.clear();
+ st.selectedDiscPath = path;
+ st.userAcceptedDiscPath = path;
+ getSettings().backend.isoPath.setValue(path);
+ config::Save();
+ refresh_state();
+ modal.pop();
+ };
+ host.push(std::make_unique(Modal::Props{
+ .title = "Disc verification",
+ .bodyRml = bodyRml,
+ .actions =
+ {
+ ModalAction{
+ .label = "Cancel",
+ .onPressed = dismiss,
+ },
+ ModalAction{
+ .label = "Continue anyway",
+ .onPressed = acceptHashMismatch,
+ },
+ },
+ .onDismiss = dismiss,
+ }));
+ return;
+ }
+
+ host.push(std::make_unique(Modal::Props{
+ .title = "Disc verification",
+ .bodyRml = state.errorString,
+ .actions =
+ {
+ ModalAction{
+ .label = "OK",
+ .onPressed = dismiss,
+ },
+ },
+ .onDismiss = dismiss,
+ }));
}
void ensure_initialized() noexcept {
@@ -117,7 +196,9 @@ void ensure_initialized() noexcept {
state.selectedDiscPath = getSettings().backend.isoPath;
state.initialDiscPath = state.selectedDiscPath;
- if (iso::validate(state.initialDiscPath.c_str()) == iso::ValidationError::Success) {
+ const auto& res = iso::validate(state.initialDiscPath.c_str());
+ if (res >= iso::ValidationError::HashMismatch) {
+ state.initialDiscValidationRes = res;
state.initialDiscIsPal = iso::isPal(state.initialDiscPath.c_str());
}
state.initialLanguage = getSettings().game.language;
@@ -287,28 +368,14 @@ void Prelaunch::update() {
ensure_initialized();
try_apply_mirrored_layout(mDocument);
- auto& state = prelaunch_state();
- if (!state.errorString.empty() && top_document() == this) {
- auto dismiss = [](Modal& modal) {
- prelaunch_state().errorString.clear();
- modal.pop();
- };
- push(std::make_unique(Modal::Props{
- .title = "Invalid disc image",
- .bodyRml = state.errorString,
- .actions =
- {
- ModalAction{
- .label = "OK",
- .onPressed = dismiss,
- },
- },
- .onDismiss = dismiss,
- }));
+ if (top_document() == this) {
+ try_push_verification_modal(*this);
}
- const bool hasValidPath = prelaunch_state().selectedDiscIsValid;
- mDocument->SetClass("disc-ready", hasValidPath);
+ const auto& state = prelaunch_state();
+
+ const bool hasValidPath = state.selectedDiscIsValid;
+ mDocument->SetClass("disc-ready", IsGameLaunched);
if (hasValidPath) {
if (getSettings().backend.skipPreLaunchUI) {
hide(true);
@@ -327,17 +394,28 @@ void Prelaunch::update() {
const auto discStatusLabel = mDiscStatus->GetElementById("disc-status-label");
+ const bool discHashMismatchAccepted =
+ hasValidPath && !state.userAcceptedDiscPath.empty() &&
+ state.selectedDiscPath == state.userAcceptedDiscPath;
+
if (mDiscStatus != nullptr && discStatusLabel != nullptr) {
- if (hasValidPath) {
+ if (state.initialDiscValidationRes == iso::ValidationError::Success) {
mDiscStatus->SetAttribute("status", "good");
discStatusLabel->SetInnerRML("Disc ready.");
+ } else if (state.initialDiscValidationRes == iso::ValidationError::HashMismatch) {
+ mDiscStatus->SetAttribute("status", "mismatch");
+ discStatusLabel->SetInnerRML("Disc hash mismatch.");
+ } else {
+ mDiscStatus->RemoveAttribute("status");
+ discStatusLabel->SetInnerRML("No disc image found.");
}
}
if (mDiscDetail != nullptr) {
if (hasValidPath) {
mDiscDetail->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Block);
- mDiscDetail->SetInnerRML(
- prelaunch_state().initialDiscIsPal ? "GameCube • EUR" : "GameCube • USA");
+ Rml::String innerRML = "GameCube • ";
+ innerRML += state.initialDiscIsPal ? "EUR" : "USA";
+ mDiscDetail->SetInnerRML(innerRML);
} else {
mDiscDetail->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::None);
}
diff --git a/src/dusk/ui/prelaunch.hpp b/src/dusk/ui/prelaunch.hpp
index e4d542cc2d..73033f9816 100644
--- a/src/dusk/ui/prelaunch.hpp
+++ b/src/dusk/ui/prelaunch.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "dusk/iso_validate.hpp"
#include "button.hpp"
#include "document.hpp"
@@ -39,12 +40,15 @@ struct PrelaunchState {
std::string selectedDiscPath;
bool selectedDiscIsValid = false;
bool selectedDiscIsPal = false;
- std::string errorString;
- bool initialDiscIsPal = false;
std::string initialDiscPath;
+ iso::ValidationError initialDiscValidationRes = iso::ValidationError::Unknown;
+ bool initialDiscIsPal = false;
GameLanguage initialLanguage = GameLanguage::English;
std::string initialGraphicsBackend;
int initialCardFileType = 0;
+ std::string errorString;
+ std::string pendingDiscPath;
+ std::string userAcceptedDiscPath;
};
PrelaunchState& prelaunch_state() noexcept;
@@ -52,5 +56,6 @@ void ensure_initialized() noexcept;
void refresh_state() noexcept;
void open_iso_picker() noexcept;
bool is_restart_pending() noexcept;
+void try_push_verification_modal(Document& host);
} // namespace dusk::ui
diff --git a/src/dusk/ui/settings.cpp b/src/dusk/ui/settings.cpp
index 7979b22116..ddb6a4cd31 100644
--- a/src/dusk/ui/settings.cpp
+++ b/src/dusk/ui/settings.cpp
@@ -17,8 +17,6 @@
#include
-#include "modal.hpp"
-
namespace dusk::ui {
namespace {
@@ -869,27 +867,8 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
}
void SettingsWindow::update() {
- // Show disc validation error message if present
if (mPrelaunch && top_document() == this) {
- auto& state = prelaunch_state();
- if (!state.errorString.empty()) {
- auto dismissInvalidDisc = [](Modal& modal) {
- prelaunch_state().errorString.clear();
- modal.pop();
- };
- push_document(std::make_unique(Modal::Props{
- .title = "Invalid disc image",
- .bodyRml = state.errorString,
- .actions =
- {
- ModalAction{
- .label = "OK",
- .onPressed = dismissInvalidDisc,
- },
- },
- .onDismiss = dismissInvalidDisc,
- }));
- }
+ try_push_verification_modal(*this);
}
Window::update();
diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp
index f7b6cd8865..e996589b59 100644
--- a/src/m_Do/m_Do_main.cpp
+++ b/src/m_Do/m_Do_main.cpp
@@ -621,7 +621,7 @@ int game_main(int argc, char* argv[]) {
// Invalidate a bad saved isoPath so that Dusk can't get blocked from starting up
const std::string p = dusk::getSettings().backend.isoPath;
- if (!p.empty() && dusk::iso::validate(p.c_str()) != dusk::iso::ValidationError::Success) {
+ if (!p.empty() && dusk::iso::validate(p.c_str()) < dusk::iso::ValidationError::HashMismatch) {
dusk::getSettings().backend.isoPath.setValue("");
}
@@ -629,7 +629,7 @@ int game_main(int argc, char* argv[]) {
bool dvd_opened = false;
if (parsed_arg_options.count("dvd")) {
dvd_path = parsed_arg_options["dvd"].as();
- if (dusk::iso::validate(dvd_path.c_str()) == dusk::iso::ValidationError::Success) {
+ if (dusk::iso::validate(dvd_path.c_str()) >= dusk::iso::ValidationError::HashMismatch) {
DuskLog.info("Loading DVD image from command line: {}", dvd_path);
dvd_opened = aurora_dvd_open(dvd_path.c_str());
if (!dvd_opened) {
@@ -668,7 +668,8 @@ int game_main(int argc, char* argv[]) {
if (dvd_path.empty()) {
DuskLog.fatal("No DVD image specified, unable to boot!");
}
- if (dusk::iso::validate(dvd_path.c_str()) != dusk::iso::ValidationError::Success) {
+ if (!dusk::IsGameLaunched &&
+ dusk::iso::validate(dvd_path.c_str()) < dusk::iso::ValidationError::HashMismatch) {
DuskLog.fatal("DVD image failed verification: {}", dvd_path);
}
DuskLog.info("Loading DVD image: {}", dvd_path);