Progress modal & persist validation state

This commit is contained in:
Luke Street
2026-05-06 23:21:43 -06:00
parent 4954685cca
commit c50d777309
13 changed files with 527 additions and 98 deletions
+13
View File
@@ -21,6 +21,12 @@ enum class GameLanguage : u8 {
Italian = OS_LANGUAGE_ITALIAN,
};
enum class DiscVerificationState : u8 {
Unknown = 0,
Success,
HashMismatch,
};
namespace config {
template <>
struct ConfigEnumRange<BloomMode> {
@@ -33,6 +39,12 @@ struct ConfigEnumRange<GameLanguage> {
static constexpr auto min = GameLanguage::English;
static constexpr auto max = GameLanguage::Italian;
};
template <>
struct ConfigEnumRange<DiscVerificationState> {
static constexpr auto min = DiscVerificationState::Unknown;
static constexpr auto max = DiscVerificationState::HashMismatch;
};
}
// Persistent user settings
@@ -150,6 +162,7 @@ struct UserSettings {
struct {
ConfigVar<std::string> isoPath;
ConfigVar<DiscVerificationState> isoVerification;
ConfigVar<std::string> graphicsBackend;
ConfigVar<bool> skipPreLaunchUI;
ConfigVar<bool> showPipelineCompilation;
+8
View File
@@ -224,6 +224,10 @@ body.mirrored version-info {
color: #FFD6A7;
}
#disc-status[status=unknown] {
color: rgba(224, 219, 200, 65%);
}
#disc-status icon {
display: none;
width: 24dp;
@@ -253,6 +257,10 @@ body.mirrored version-info {
decorator: text("&#xe002;" center center);
}
#disc-status[status=unknown] icon {
decorator: text("&#xe887;" center center);
}
#disc-version {
font-size: 20dp;
}
+37
View File
@@ -279,6 +279,10 @@ icon.error {
decorator: text("&#xe000;" center center);
}
icon.verifying {
decorator: text("&#xe8b5;" center center);
}
icon.celebration {
decorator: text("&#xea65;" center center);
}
@@ -399,6 +403,10 @@ button.achievement-clear {
text-align: left;
}
window.modal.danger {
border: 2dp #852221;
}
.modal-header {
display: flex;
flex-direction: row;
@@ -424,6 +432,11 @@ button.achievement-clear {
flex: 1 1 auto;
}
window.modal.danger .modal-title,
window.modal.danger .modal-header icon {
color: #B3261E;
}
.modal-body {
display: block;
width: 100%;
@@ -434,6 +447,30 @@ button.achievement-clear {
font-weight: normal;
}
.verification-progress {
display: flex;
flex-direction: column;
gap: 10dp;
width: 100%;
}
.verification-file {
display: block;
font-size: 17dp;
color: #FFFFFF;
}
progressbar.verification-progress-bar {
height: 8dp;
margin: 2dp 0 0 0;
}
.verification-detail {
display: block;
font-size: 14dp;
color: rgba(224, 219, 200, 65%);
}
.modal-actions {
display: flex;
flex-direction: row;
+1
View File
@@ -154,6 +154,7 @@ namespace dusk::config {
template class ConfigImpl<f64>;
template class ConfigImpl<std::string>;
template class ConfigImpl<dusk::BloomMode>;
template class ConfigImpl<dusk::DiscVerificationState>;
template class ConfigImpl<dusk::GameLanguage>;
}
+43 -19
View File
@@ -1,8 +1,13 @@
#include "iso_validate.hpp"
#include <nod.h>
#include <SDL3/SDL_iostream.h>
#include <nod.h>
#include <xxhash.h>
#include <array>
#include <memory>
#include <stdexcept>
#include <string_view>
namespace {
@@ -62,8 +67,8 @@ struct KnownDisc {
constexpr KnownDisc(std::string_view id, Platform platform, Region region)
: id(id), platform(platform), region(region) {}
constexpr KnownDisc(std::string_view id, Platform platform, Region region,
const std::string_view hash)
constexpr KnownDisc(
std::string_view id, Platform platform, Region region, const std::string_view hash)
: id(id), platform(platform), region(region), supported(true), hash(parse_xxh128(hash)) {}
};
@@ -144,22 +149,30 @@ void StreamClose(void* user_data) {
}
ValidationError verify_disc(NodHandle* disc, VerificationStatus& status) {
const auto hashState = XXH3_createState();
XXH3_128bits_reset(hashState);
std::unique_ptr<XXH3_state_t, decltype(&XXH3_freeState)> hashState(
XXH3_createState(), XXH3_freeState);
if (!hashState) {
return ValidationError::Unknown;
}
XXH3_128bits_reset(hashState.get());
while (true) {
if (status.shouldCancel.load(std::memory_order_relaxed)) {
return ValidationError::Canceled;
}
size_t bytesAvail;
const auto buf = nod_buf_read(disc, &bytesAvail);
if (!bytesAvail)
break;
XXH3_128bits_update(hashState, buf, bytesAvail);
XXH3_128bits_update(hashState.get(), buf, bytesAvail);
status.bytesRead += bytesAvail;
status.bytesRead.fetch_add(bytesAvail, std::memory_order_relaxed);
nod_buf_consume(disc, bytesAvail);
}
const auto hash = XXH3_128bits_digest(hashState);
const auto hash = XXH3_128bits_digest(hashState.get());
if (!XXH128_isEqual(hash, status.knownDisc->hash)) {
return ValidationError::HashMismatch;
}
@@ -175,7 +188,7 @@ ValidationError validate(const char* path, VerificationStatus& status) {
return ValidationError::IOError;
}
const NodDiscStream nod_stream {
const NodDiscStream nod_stream{
.user_data = sdlStream,
.read_at = StreamReadAt,
.stream_len = StreamLength,
@@ -187,7 +200,7 @@ ValidationError validate(const char* path, VerificationStatus& status) {
return convertNodError(result);
}
status.bytesTotal = nod_disc_size(disc.handle);
status.bytesTotal.store(nod_disc_size(disc.handle), std::memory_order_relaxed);
NodDiscHeader header{};
result = nod_disc_header(disc.handle, &header);
@@ -215,12 +228,12 @@ ValidationError validate(const char* path) {
return validate(path, status);
}
bool isPal(const char* path) {
ValidationError inspect(const char* path, DiscInfo& info) {
NodHandleWrapper disc;
const auto sdlStream = SDL_IOFromFile(path, "rb");
if (sdlStream == nullptr) {
return false;
return ValidationError::IOError;
}
const NodDiscStream nod_stream{
@@ -230,19 +243,30 @@ bool isPal(const char* path) {
.close = StreamClose,
};
if (nod_disc_open_stream(&nod_stream, nullptr, &disc.handle) != NOD_RESULT_OK ||
disc.handle == nullptr)
{
return false;
auto result = nod_disc_open_stream(&nod_stream, nullptr, &disc.handle);
if (disc.handle == nullptr || result != NOD_RESULT_OK) {
return convertNodError(result);
}
NodDiscHeader header{};
if (nod_disc_header(disc.handle, &header) != NOD_RESULT_OK) {
return false;
result = nod_disc_header(disc.handle, &header);
if (result != NOD_RESULT_OK) {
return convertNodError(result);
}
const auto knownDisc = find_disc(std::string_view(header.game_id, 6));
if (!knownDisc) {
return ValidationError::WrongGame;
}
info.isPal = knownDisc->region == Region::Europe;
if (!knownDisc->supported) {
return ValidationError::WrongVersion;
}
return ValidationError::Success;
}
return knownDisc && knownDisc->region == Region::Europe;
bool isPal(const char* path) {
DiscInfo info{};
return inspect(path, info) == ValidationError::Success && info.isPal;
}
} // namespace dusk::iso
+28 -21
View File
@@ -1,31 +1,38 @@
#ifndef DUSK_ISO_VALIDATE_HPP
#define DUSK_ISO_VALIDATE_HPP
#include <xxh3.h>
#include <atomic>
namespace dusk::iso {
struct KnownDisc;
struct KnownDisc;
enum class ValidationError : u8 {
Unknown = 0,
IOError,
InvalidImage,
WrongGame,
WrongVersion,
HashMismatch,
Success
};
enum class ValidationError : u8 {
Unknown = 0,
IOError,
InvalidImage,
WrongGame,
WrongVersion,
Canceled,
HashMismatch,
Success
};
struct VerificationStatus {
size_t bytesRead = 0;
size_t bytesTotal = 0;
const KnownDisc* knownDisc = nullptr;
bool shouldCancel = false;
};
struct VerificationStatus {
std::atomic_size_t bytesRead = 0;
std::atomic_size_t bytesTotal = 0;
const KnownDisc* knownDisc = nullptr;
std::atomic_bool shouldCancel = false;
};
ValidationError validate(const char* path, VerificationStatus& status);
ValidationError validate(const char* path);
bool isPal(const char* path);
}
struct DiscInfo {
bool isPal = false;
};
ValidationError inspect(const char* path, DiscInfo& info);
ValidationError validate(const char* path, VerificationStatus& status);
ValidationError validate(const char* path);
bool isPal(const char* path);
} // namespace dusk::iso
#endif // DUSK_ISO_VALIDATE_HPP
+2
View File
@@ -109,6 +109,7 @@ UserSettings g_userSettings = {
.backend = {
.isoPath {"backend.isoPath", ""},
.isoVerification {"backend.isoVerification", DiscVerificationState::Unknown},
.graphicsBackend {"backend.graphicsBackend", "auto"},
.skipPreLaunchUI {"backend.skipPreLaunchUI", false},
.showPipelineCompilation {"backend.showPipelineCompilation", false},
@@ -206,6 +207,7 @@ void registerSettings() {
Register(g_userSettings.game.debugFlyCam);
Register(g_userSettings.backend.isoPath);
Register(g_userSettings.backend.isoVerification);
Register(g_userSettings.backend.graphicsBackend);
Register(g_userSettings.backend.skipPreLaunchUI);
Register(g_userSettings.backend.showPipelineCompilation);
+6 -3
View File
@@ -2,8 +2,11 @@
namespace dusk::ui {
Modal::Modal(Props props)
: WindowSmall("modal", "modal-dialog"), mProps(std::move(props)) {
Modal::Modal(Props props) : WindowSmall("modal", "modal-dialog"), mProps(std::move(props)) {
if (!mProps.variant.empty()) {
mRoot->SetClass(mProps.variant, true);
}
auto* header = append(mDialog, "div");
header->SetClass("modal-header", true);
@@ -14,7 +17,7 @@ Modal::Modal(Props props)
if (mProps.isWarning) {
auto* icon = append(header, "icon");
icon->SetClass("warning", true);
} else if ( mProps.isError ) {
} else if (mProps.isError) {
auto* icon = append(header, "icon");
icon->SetClass("error", true);
}
+1
View File
@@ -18,6 +18,7 @@ public:
Rml::String bodyRml;
std::vector<ModalAction> actions;
std::function<void(Modal&)> onDismiss;
Rml::String variant;
bool isWarning = false;
bool isError = false;
};
+2 -1
View File
@@ -189,7 +189,8 @@ void Overlay::update() {
const bool showControllerWarning = PADGetIndexForPort(PAD_CHAN0) < 0 &&
PADGetKeyButtonBindings(PAD_CHAN0, nullptr) == nullptr &&
dynamic_cast<Window*>(top_document()) == nullptr;
dynamic_cast<Window*>(top_document()) == nullptr &&
dynamic_cast<WindowSmall*>(top_document()) == nullptr;
if (showControllerWarning && mControllerWarning == nullptr) {
mControllerWarning = create_controller_warning(mDocument);
} else if (showControllerWarning && mControllerWarning != nullptr) {
+373 -46
View File
@@ -11,11 +11,23 @@
#include "version.h"
#include <SDL3/SDL_dialog.h>
#include <aurora/lib/logging.hpp>
#include <aurora/lib/window.hpp>
#include <fmt/format.h>
#include <algorithm>
#include <array>
#include <atomic>
#include <exception>
#include <filesystem>
#include <optional>
#include <thread>
#include "m_Do/m_Do_MemCard.h"
namespace dusk::ui {
namespace {
aurora::Module PrelaunchLog{"dusk::ui::prelaunch"};
const Rml::String kDocumentSource = R"RML(
<rml>
@@ -54,7 +66,130 @@ constexpr std::array<SDL_DialogFileFilter, 2> kDiscFileFilters{{
{"All Files", "*"},
}};
static std::string get_error_msg(iso::ValidationError error) {
struct DiscVerificationResult {
std::string path;
iso::ValidationError validation = iso::ValidationError::Unknown;
};
struct DiscVerificationTask {
explicit DiscVerificationTask(std::string discPath) : path(std::move(discPath)) {
worker = std::thread([this] {
try {
validation = iso::validate(path.c_str(), status);
} catch (const std::exception& e) {
PrelaunchLog.error(
"Disc verification failed with exception for '{}': {}", path, e.what());
validation = iso::ValidationError::Unknown;
} catch (...) {
PrelaunchLog.error(
"Disc verification failed with unknown exception for '{}'", path);
validation = iso::ValidationError::Unknown;
}
done.store(true, std::memory_order_release);
});
}
~DiscVerificationTask() {
status.shouldCancel.store(true, std::memory_order_relaxed);
join();
}
void join() {
if (worker.joinable()) {
worker.join();
}
}
[[nodiscard]] bool finished() const { return done.load(std::memory_order_acquire); }
std::string path;
iso::VerificationStatus status;
iso::ValidationError validation = iso::ValidationError::Unknown;
std::atomic_bool done = false;
std::thread worker;
};
std::unique_ptr<DiscVerificationTask> sDiscVerificationTask;
bool sDiscVerificationModalPushed = false;
bool validation_allows_launch(iso::ValidationError validation) noexcept {
return validation == iso::ValidationError::Success ||
validation == iso::ValidationError::HashMismatch ||
validation == iso::ValidationError::Canceled;
}
bool verification_state_allows_launch(iso::ValidationError validation) noexcept {
return validation == iso::ValidationError::Unknown ||
validation == iso::ValidationError::Success ||
validation == iso::ValidationError::HashMismatch ||
validation == iso::ValidationError::Canceled;
}
iso::ValidationError verification_from_config(DiscVerificationState value) noexcept {
switch (value) {
case DiscVerificationState::Success:
return iso::ValidationError::Success;
case DiscVerificationState::HashMismatch:
return iso::ValidationError::HashMismatch;
default:
return iso::ValidationError::Unknown;
}
}
DiscVerificationState verification_to_config(iso::ValidationError validation) {
switch (validation) {
case iso::ValidationError::Success:
return DiscVerificationState::Success;
case iso::ValidationError::HashMismatch:
return DiscVerificationState::HashMismatch;
default:
return DiscVerificationState::Unknown;
}
}
std::string format_bytes(std::size_t bytes) {
constexpr double KiB = 1024.0;
constexpr double MiB = KiB * 1024.0;
constexpr double GiB = MiB * 1024.0;
if (bytes >= static_cast<std::size_t>(GiB)) {
return fmt::format("{:.2f} GiB", static_cast<double>(bytes) / GiB);
}
if (bytes >= static_cast<std::size_t>(MiB)) {
return fmt::format("{:.0f} MiB", static_cast<double>(bytes) / MiB);
}
if (bytes >= static_cast<std::size_t>(KiB)) {
return fmt::format("{:.0f} KiB", static_cast<double>(bytes) / KiB);
}
return fmt::format("{} B", bytes);
}
void begin_disc_verification(std::string path) noexcept {
if (path.empty()) {
return;
}
if (sDiscVerificationTask != nullptr) {
sDiscVerificationTask->status.shouldCancel.store(true, std::memory_order_relaxed);
sDiscVerificationTask.reset();
}
sDiscVerificationTask = std::make_unique<DiscVerificationTask>(std::move(path));
sDiscVerificationModalPushed = false;
}
std::optional<DiscVerificationResult> take_finished_disc_verification() {
if (sDiscVerificationTask == nullptr || !sDiscVerificationTask->finished()) {
return std::nullopt;
}
DiscVerificationResult result{
.path = sDiscVerificationTask->path,
.validation = sDiscVerificationTask->validation,
};
sDiscVerificationTask->join();
sDiscVerificationTask.reset();
sDiscVerificationModalPushed = false;
return result;
}
std::string get_error_msg(iso::ValidationError error) {
switch (error) {
default:
return "The selected disc image could not be validated.";
@@ -66,40 +201,215 @@ static std::string get_error_msg(iso::ValidationError error) {
return "The selected game is not supported by Dusk.";
case iso::ValidationError::WrongVersion:
return "Dusk currently supports GameCube USA and PAL disc images only.";
case iso::ValidationError::Canceled:
return "Disc verification was canceled. Dusk cannot guarantee the selected disc image "
"is compatible.";
case iso::ValidationError::HashMismatch:
return "The selected disc image did not pass hash verification, it may be corrupt or modified.";
return "The selected disc image did not pass hash verification. It may be corrupt or "
"modified.";
case iso::ValidationError::Success:
return "The selected disc image is valid.";
}
}
void persist_disc_choice(const std::string& path, iso::ValidationError validation) {
getSettings().backend.isoPath.setValue(path);
getSettings().backend.isoVerification.setValue(verification_to_config(validation));
config::Save();
}
void apply_valid_disc_result(const std::string& path, iso::ValidationError validation) {
auto& state = prelaunch_state();
iso::DiscInfo info{};
const bool pal =
iso::inspect(path.c_str(), info) == iso::ValidationError::Success && info.isPal;
state.selectedDiscPath = path;
state.selectedDiscIsValid = true;
state.selectedDiscIsPal = pal;
state.initialDiscValidationRes = validation;
state.initialDiscIsPal = pal;
persist_disc_choice(path, validation);
}
void apply_disc_verification_result(const DiscVerificationResult& result) {
auto& state = prelaunch_state();
if (result.validation == iso::ValidationError::HashMismatch ||
result.validation == iso::ValidationError::Canceled)
{
state.pendingDiscPath = result.path;
state.pendingDiscValidation = result.validation;
state.errorString = escape(get_error_msg(result.validation));
return;
}
if (validation_allows_launch(result.validation)) {
apply_valid_disc_result(result.path, result.validation);
if (result.validation == iso::ValidationError::Success) {
state.errorString.clear();
state.pendingDiscPath.clear();
state.pendingDiscValidation = iso::ValidationError::Unknown;
}
return;
}
state.pendingDiscPath.clear();
state.pendingDiscValidation = iso::ValidationError::Unknown;
state.errorString = escape(get_error_msg(result.validation));
}
class DiscVerificationModal : public WindowSmall {
public:
DiscVerificationModal() : WindowSmall("modal", "modal-dialog") {
auto* header = append(mDialog, "div");
header->SetClass("modal-header", true);
auto* title = append(header, "div");
title->SetClass("modal-title", true);
title->SetInnerRML("Verifying disc image");
auto* icon = append(header, "icon");
icon->SetClass("verifying", true);
auto* body = append(mDialog, "div");
body->SetClass("modal-body", true);
auto* content = append(body, "div");
content->SetClass("verification-progress", true);
mFileName = append(content, "div");
mFileName->SetClass("verification-file", true);
mProgress = append(content, "progressbar");
mProgress->SetClass("progress-ongoing", true);
mProgress->SetClass("verification-progress-bar", true);
mProgress->SetAttribute("value", 0.f);
mDetail = append(content, "div");
mDetail->SetClass("verification-detail", true);
auto* actions = append(mDialog, "div");
actions->SetClass("modal-actions", true);
mCancelButton = std::make_unique<Button>(actions, "Cancel");
mCancelButton->root()->SetClass("modal-btn", true);
mCancelButton->on_pressed([this] { request_cancel(); });
refresh();
}
void update() override {
if (mFinished) {
return;
}
if (auto result = take_finished_disc_verification()) {
mFinished = true;
apply_disc_verification_result(*result);
pop();
return;
}
if (sDiscVerificationTask == nullptr) {
mFinished = true;
pop();
return;
}
refresh();
}
bool focus() override { return mCancelButton != nullptr && mCancelButton->focus(); }
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override {
if (cmd == NavCommand::Cancel || cmd == NavCommand::Menu) {
request_cancel();
event.StopPropagation();
return true;
}
if (cmd == NavCommand::Left || cmd == NavCommand::Right) {
return true;
}
return false;
}
private:
void request_cancel() {
if (sDiscVerificationTask == nullptr || mCancelRequested) {
return;
}
mCancelRequested = true;
sDiscVerificationTask->status.shouldCancel.store(true, std::memory_order_relaxed);
if (mCancelButton != nullptr) {
mCancelButton->set_text("Cancelling...");
mCancelButton->set_disabled(true);
}
}
void refresh() {
if (sDiscVerificationTask == nullptr) {
return;
}
if (mCancelRequested) {
return;
}
if (mFileName != nullptr) {
std::string fileName =
std::filesystem::path(sDiscVerificationTask->path).filename().string();
if (fileName.empty()) {
fileName = sDiscVerificationTask->path;
}
mFileName->SetInnerRML(escape(fileName));
}
const std::size_t bytesRead =
sDiscVerificationTask->status.bytesRead.load(std::memory_order_relaxed);
const std::size_t bytesTotal =
sDiscVerificationTask->status.bytesTotal.load(std::memory_order_relaxed);
if (bytesTotal == 0) {
if (mProgress != nullptr) {
mProgress->SetAttribute("value", 0.f);
}
if (mDetail != nullptr) {
mDetail->SetInnerRML("Opening disc image...");
}
return;
}
const float fraction =
std::clamp(static_cast<float>(bytesRead) / static_cast<float>(bytesTotal), 0.0f, 1.0f);
if (mProgress != nullptr) {
mProgress->SetAttribute("value", fraction);
}
if (mDetail != nullptr) {
mDetail->SetInnerRML(escape(fmt::format("{} / {} ({:.0f}%)", format_bytes(bytesRead),
format_bytes(bytesTotal), fraction * 100.0f)));
}
}
Rml::Element* mFileName = nullptr;
Rml::Element* mProgress = nullptr;
Rml::Element* mDetail = nullptr;
std::unique_ptr<Button> mCancelButton;
bool mCancelRequested = false;
bool mFinished = false;
};
void file_dialog_callback(void*, const char* path, const char* error) {
if (path == nullptr || error != nullptr) {
return;
}
auto& state = prelaunch_state();
const auto validation = iso::validate(path);
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;
}
if (validation == iso::ValidationError::HashMismatch) {
state.pendingDiscPath = path;
} else {
state.pendingDiscPath.clear();
}
state.errorString = escape(get_error_msg(validation));
begin_disc_verification(path);
}
PrelaunchState sPrelaunchState;
} // namespace
PrelaunchState& prelaunch_state() noexcept {
return sPrelaunchState;
}
@@ -108,28 +418,47 @@ void refresh_state() noexcept {
auto& state = prelaunch_state();
if (state.selectedDiscPath.empty()) {
state.selectedDiscIsValid = false;
return;
}
if (!state.userAcceptedDiscPath.empty() &&
state.selectedDiscPath == state.userAcceptedDiscPath) {
state.selectedDiscIsValid = true;
state.selectedDiscIsPal = iso::isPal(state.selectedDiscPath.c_str());
state.selectedDiscIsPal = false;
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();
iso::DiscInfo info{};
const auto metadataValidation = iso::inspect(state.selectedDiscPath.c_str(), info);
if (metadataValidation != iso::ValidationError::Success) {
state.selectedDiscIsValid = false;
state.selectedDiscIsPal = false;
state.initialDiscValidationRes = metadataValidation;
state.initialDiscIsPal = false;
return;
}
auto verification = state.initialDiscValidationRes;
if (state.selectedDiscPath == getSettings().backend.isoPath.getValue()) {
verification = verification_from_config(getSettings().backend.isoVerification.getValue());
}
if (verification_state_allows_launch(verification)) {
state.selectedDiscIsValid = true;
state.selectedDiscIsPal = info.isPal;
state.initialDiscValidationRes = verification;
state.initialDiscIsPal = info.isPal;
return;
}
state.selectedDiscIsValid = false;
state.selectedDiscIsPal = false;
state.initialDiscValidationRes = iso::ValidationError::Unknown;
state.initialDiscIsPal = false;
}
void try_push_verification_modal(Document& host) {
auto& state = prelaunch_state();
if (sDiscVerificationTask != nullptr && !sDiscVerificationModalPushed) {
sDiscVerificationModalPushed = true;
host.push(std::make_unique<DiscVerificationModal>());
return;
}
if (state.errorString.empty()) {
return;
}
@@ -138,20 +467,21 @@ void try_push_verification_modal(Document& host) {
auto& state = prelaunch_state();
state.errorString.clear();
state.pendingDiscPath.clear();
state.pendingDiscValidation = iso::ValidationError::Unknown;
modal.pop();
};
if (!state.pendingDiscPath.empty()) {
const Rml::String bodyRml = state.errorString + "<br/><br/>You may proceed at your own risk.";
const Rml::String bodyRml =
state.errorString + "<br/><br/>You may proceed at your own risk.";
auto acceptHashMismatch = [](Modal& modal) {
auto& st = prelaunch_state();
std::string path = std::move(st.pendingDiscPath);
const auto validation = st.pendingDiscValidation;
st.pendingDiscPath.clear();
st.pendingDiscValidation = iso::ValidationError::Unknown;
st.errorString.clear();
st.selectedDiscPath = path;
st.userAcceptedDiscPath = path;
getSettings().backend.isoPath.setValue(path);
config::Save();
apply_valid_disc_result(path, validation);
refresh_state();
modal.pop();
};
@@ -170,6 +500,7 @@ void try_push_verification_modal(Document& host) {
},
},
.onDismiss = dismiss,
.variant = "danger",
.isWarning = true,
}));
return;
@@ -198,11 +529,8 @@ void ensure_initialized() noexcept {
state.selectedDiscPath = getSettings().backend.isoPath;
state.initialDiscPath = state.selectedDiscPath;
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.initialDiscValidationRes =
verification_from_config(getSettings().backend.isoVerification.getValue());
state.initialLanguage = getSettings().game.language;
state.initialGraphicsBackend = getSettings().backend.graphicsBackend;
state.initialCardFileType = getSettings().backend.cardFileType;
@@ -397,10 +725,6 @@ 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 (state.initialDiscValidationRes == iso::ValidationError::Success) {
mDiscStatus->SetAttribute("status", "good");
@@ -408,6 +732,9 @@ void Prelaunch::update() {
} else if (state.initialDiscValidationRes == iso::ValidationError::HashMismatch) {
mDiscStatus->SetAttribute("status", "mismatch");
discStatusLabel->SetInnerRML("Disc hash mismatch.");
} else if (hasValidPath) {
mDiscStatus->SetAttribute("status", "unknown");
discStatusLabel->SetInnerRML("Disc not verified.");
} else {
mDiscStatus->RemoveAttribute("status");
discStatusLabel->SetInnerRML("No disc image found.");
+1 -1
View File
@@ -48,7 +48,7 @@ struct PrelaunchState {
int initialCardFileType = 0;
std::string errorString;
std::string pendingDiscPath;
std::string userAcceptedDiscPath;
iso::ValidationError pendingDiscValidation = iso::ValidationError::Unknown;
};
PrelaunchState& prelaunch_state() noexcept;
+12 -7
View File
@@ -172,7 +172,7 @@ Document& push_document(std::unique_ptr<Document> doc, bool show, bool passive)
if (passive) {
sPassiveDocuments.push_back(std::move(doc));
} else {
sDocumentStack.push_back({std::move(doc)});
sDocumentStack.push_back(std::move(doc));
}
if (show) {
ret.show();
@@ -215,12 +215,17 @@ void update() noexcept {
}
input::update_input();
for (const auto& doc : sDocumentStack) {
doc->update();
}
for (const auto& doc : sPassiveDocuments) {
doc->update();
}
const auto update_documents = [](auto& documents) {
const std::size_t count = documents.size();
for (std::size_t i = 0; i < count && i < documents.size(); ++i) {
Document* doc = documents[i].get();
if (doc != nullptr && !doc->closed()) {
doc->update();
}
}
};
update_documents(sDocumentStack);
update_documents(sPassiveDocuments);
// Remove closed documents
{