From 2e8c1096fdb43267bbd801b58bf6730ec23ae3df Mon Sep 17 00:00:00 2001 From: OverkillLabs <10800190+OverkillLabs@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:45:15 +0300 Subject: [PATCH] feat(libSceMsgDialog): real dialog state machine, auto-answer without UI Replace the unimplemented stubs with a working NONE -> INITIALIZED -> RUNNING -> FINISHED state machine for the message dialog PRX. Open() best-effort parses the SceMsgDialogParam layout to pick a sensible default button (auto-focus-no variants answer No, everything else answers Yes/OK), then auto-finishes the dialog after a couple of status polls so a title that waits on a dialog for user input never blocks forever. Progress-bar mode is instead closed by the title itself via sceMsgDialogClose, with a time cap as a safety net. GetResult now fills in a real SceMsgDialogResult (mode/result/buttonId) instead of leaving the caller's buffer untouched. --- core/libs/HitLog.hpp | 18 +++ core/libs/prx/libSceMsgDialog/Export.cpp | 195 ++++++++++++++++++----- 2 files changed, 169 insertions(+), 44 deletions(-) create mode 100644 core/libs/HitLog.hpp diff --git a/core/libs/HitLog.hpp b/core/libs/HitLog.hpp new file mode 100644 index 000000000..e94e3228b --- /dev/null +++ b/core/libs/HitLog.hpp @@ -0,0 +1,18 @@ +#ifndef CORE_LIBS_HITLOG_HPP +#define CORE_LIBS_HITLOG_HPP + +#include +#include + +// Logs the first three calls of the surrounding call site, then stays silent (keeps guest logs usable). +#define APS5_HIT(tag, ...) \ + do { \ + static std::atomic aps5HitCount{0}; \ + if (aps5HitCount.fetch_add(1, std::memory_order_relaxed) < 3) { \ + std::fprintf(stderr, "[" tag "] " __VA_ARGS__); \ + std::fputc('\n', stderr); \ + std::fflush(stderr); \ + } \ + } while (0) + +#endif diff --git a/core/libs/prx/libSceMsgDialog/Export.cpp b/core/libs/prx/libSceMsgDialog/Export.cpp index f826fe36a..1e02b26ea 100644 --- a/core/libs/prx/libSceMsgDialog/Export.cpp +++ b/core/libs/prx/libSceMsgDialog/Export.cpp @@ -1,66 +1,173 @@ +// libSceMsgDialog: message dialog state machine (NONE -> INITIALIZED -> RUNNING -> FINISHED) without a visible dialog. +// The dialog "closes" with the affirmative button after a couple of status polls, so a title waiting for the user +// never blocks. The message text is logged for the first calls. +#include #include +#include #include +#include +#include +#include + #include "SceTypes.hpp" +#include "HitLog.hpp" #include "prx/libc/include/General.hpp" +#ifdef _WIN32 +#include +#endif + +namespace { +constexpr int kStatusNone = 0; +constexpr int kStatusInitialized = 1; +constexpr int kStatusRunning = 2; +constexpr int kStatusFinished = 3; + +constexpr int kErrOk = 0; +constexpr int kErrNotInitialized = static_cast(0x80B80003u); +constexpr int kErrNotFinished = static_cast(0x80B80005u); +constexpr int kErrBusy = static_cast(0x80B80007u); +constexpr int kErrNotRunning = static_cast(0x80B8000Bu); +constexpr int kErrArgNull = static_cast(0x80B8000Du); + +constexpr int kModeUserMsg = 1; +constexpr int kModeProgressBar = 2; +constexpr int kModeSystemMsg = 3; + +constexpr int kButtonTypeOk = 0; +constexpr int kButtonTypeYesNo = 1; +constexpr int kButtonTypeNone = 2; +constexpr int kButtonTypeOkCancel = 3; +constexpr int kButtonTypeYesNoFocusNo = 4; + +constexpr int kButtonIdOk = 1; // OK / YES / BUTTON1 +constexpr int kButtonIdNo = 2; // NO / BUTTON2 +constexpr int kResultOk = 0; + +std::mutex g_lock; +int g_status = kStatusNone; +int g_mode = kModeUserMsg; +int g_buttonId = kButtonIdOk; +int g_pollsLeft = 0; +std::chrono::steady_clock::time_point g_openedAt; + +bool Readable(const void* p, std::size_t n) { +#ifdef _WIN32 + if (p == nullptr) return false; + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(p, &mbi, sizeof(mbi)) == 0 || mbi.State != MEM_COMMIT) return false; + if ((mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) return false; + const auto end = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; + return reinterpret_cast(p) + n <= end; +#else + return p != nullptr; +#endif +} + +// Best-effort inspection of SceMsgDialogParam: { SceCommonDialogBaseParam base (0x30); size_t size; int mode (0x38); +// UserMessageParam* userMsg (0x40); ProgressBarParam* progress (0x48); SystemMessageParam* system (0x50); ... }. +void Inspect(const void* param) { + const std::uint8_t* bytes = static_cast(param); + g_mode = kModeUserMsg; + g_buttonId = kButtonIdOk; + if (!Readable(bytes, 0x60)) return; + std::int32_t mode = 0; + std::memcpy(&mode, bytes + 0x38, 4); + if (mode >= kModeUserMsg && mode <= kModeSystemMsg) g_mode = mode; + if (g_mode != kModeUserMsg) return; + const std::uint8_t* userMsg = nullptr; + std::memcpy(&userMsg, bytes + 0x40, 8); + if (!Readable(userMsg, 16)) return; + std::int32_t buttonType = 0; + std::memcpy(&buttonType, userMsg, 4); + const char* msg = nullptr; + std::memcpy(&msg, userMsg + 8, 8); + if (buttonType == kButtonTypeYesNoFocusNo) g_buttonId = kButtonIdNo; + if (Readable(msg, 4)) { + char text[161] = {}; + for (int i = 0; i < 160 && Readable(msg + i, 1) && msg[i] != '\0'; ++i) text[i] = (msg[i] >= 0x20 || msg[i] == '\n') ? msg[i] : '?'; + APS5_HIT("MSGDIALOG", "open: buttons=%d text=\"%s\" -> auto answer", buttonType, text); + } +} + +int Poll() { + std::lock_guard g(g_lock); + if (g_status == kStatusRunning) { + bool done = false; + if (g_mode == kModeProgressBar) { + // Progress dialogs are ended by the title (sceMsgDialogClose); the cap only prevents a wait without end. + done = std::chrono::steady_clock::now() - g_openedAt > std::chrono::seconds(30); + } else { + done = --g_pollsLeft <= 0; + } + if (done) g_status = kStatusFinished; + } + return g_status; +} +} // namespace + extern "C" { -int APS5_VABI sceMsgDialogClose(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceMsgDialogGetResult(void* result) { - (void)result; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceMsgDialogGetStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceMsgDialogInitialize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard g(g_lock); + if (g_status == kStatusNone) g_status = kStatusInitialized; + APS5_HIT("MSGDIALOG", "sceMsgDialogInitialize"); + return kErrOk; +} +int APS5_VABI sceMsgDialogTerminate(void) { + std::lock_guard g(g_lock); + const bool was = g_status != kStatusNone; + g_status = kStatusNone; + return was ? kErrOk : kErrNotInitialized; } - int APS5_VABI sceMsgDialogOpen(const void* param) { - (void)param; - NotImplemented_nid_no_patch(__func__); - return 0; + if (param == nullptr) return kErrArgNull; + std::lock_guard g(g_lock); + if (g_status == kStatusRunning) return kErrBusy; + // A title that skipped sceMsgDialogInitialize still gets its dialog (and its FINISHED status). + Inspect(param); + g_pollsLeft = 2; + g_openedAt = std::chrono::steady_clock::now(); + g_status = kStatusRunning; + return kErrOk; } - -int APS5_VABI sceMsgDialogProgressBarInc(int target, uint32_t delta) { +int APS5_VABI sceMsgDialogGetStatus(void) { + return Poll(); +} +int APS5_VABI sceMsgDialogUpdateStatus(void) { + return Poll(); +} +// SceMsgDialogResult: { int32 mode; int32 result; int32 buttonId; uint8 reserved[32] }. +int APS5_VABI sceMsgDialogGetResult(void* result) { + if (result == nullptr) return kErrArgNull; + std::lock_guard g(g_lock); + if (g_status != kStatusFinished) return kErrNotFinished; + std::int32_t out[3] = {g_mode, kResultOk, g_buttonId}; + std::memcpy(result, out, sizeof(out)); + return kErrOk; +} +int APS5_VABI sceMsgDialogClose(void) { + std::lock_guard g(g_lock); + if (g_status != kStatusRunning) return kErrNotRunning; + g_status = kStatusFinished; + return kErrOk; +} +int APS5_VABI sceMsgDialogProgressBarInc(int target, std::uint32_t delta) { (void)target; (void)delta; - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard g(g_lock); + return g_status == kStatusRunning ? kErrOk : kErrNotRunning; } - int APS5_VABI sceMsgDialogProgressBarSetMsg(int target, const char* msg) { (void)target; (void)msg; - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard g(g_lock); + return g_status == kStatusRunning ? kErrOk : kErrNotRunning; } - -int APS5_VABI sceMsgDialogProgressBarSetValue(int target, uint32_t rate) { +int APS5_VABI sceMsgDialogProgressBarSetValue(int target, std::uint32_t rate) { (void)target; (void)rate; - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard g(g_lock); + return g_status == kStatusRunning ? kErrOk : kErrNotRunning; } - -int APS5_VABI sceMsgDialogTerminate(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceMsgDialogUpdateStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - }