Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Common/Data/Text/Parsers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <string>

#include "Common/Data/Text/Parsers.h"
#include "Common/Data/Text/I18n.h"
#include "Common/StringUtils.h"

// Not strictly a parser...
Expand All @@ -29,6 +30,19 @@ std::string NiceSizeFormat(uint64_t size) {
return std::string(buffer);
}

std::string NiceTimeFormat(int seconds) {
auto di = GetI18NCategory(I18NCat::DIALOG);
if (seconds < 60) {
return StringFromFormat("%d seconds", seconds);
} else if (seconds < 60 * 60) {
int minutes = seconds / 60;
return StringFromFormat("%d minutes", minutes);
} else {
int hours = seconds / 3600;
return StringFromFormat("%d hours", hours);
}
}

bool Version::ParseVersionString(std::string str) {
if (str.empty())
return false;
Expand Down
4 changes: 4 additions & 0 deletions Common/Data/Text/Parsers.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,7 @@ static bool TryParse(const std::string &str, N *const output) {
void NiceSizeFormat(uint64_t size, char *out, size_t bufSize);

std::string NiceSizeFormat(uint64_t size);

// seconds, or minutes, or hours.
// Uses I18N strings.
std::string NiceTimeFormat(int seconds);
1 change: 1 addition & 0 deletions Core/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ static const ConfigSetting generalSettings[] = {
ConfigSetting("RemoteTab", &g_Config.bRemoteTab, false, CfgFlag::DEFAULT),
ConfigSetting("RemoteISOSharedDir", &g_Config.sRemoteISOSharedDir, "", CfgFlag::DEFAULT),
ConfigSetting("RemoteISOShareType", &g_Config.iRemoteISOShareType, (int)RemoteISOShareType::RECENT, CfgFlag::DEFAULT),
ConfigSetting("AskForExitConfirmationAfterSeconds", &g_Config.iAskForExitConfirmationAfterSeconds, 60, CfgFlag::PER_GAME),

#ifdef __ANDROID__
ConfigSetting("ScreenRotation", &g_Config.iScreenRotation, ROTATION_AUTO_HORIZONTAL),
Expand Down
1 change: 1 addition & 0 deletions Core/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ struct Config {
bool bMemStickInserted;
int iMemStickSizeGB;
bool bLoadPlugins;
int iAskForExitConfirmationAfterSeconds;

int iScreenRotation; // The rotation angle of the PPSSPP UI. Only supported on Android and possibly other mobile platforms.
int iInternalScreenRotation; // The internal screen rotation angle. Useful for vertical SHMUPs and similar.
Expand Down
20 changes: 18 additions & 2 deletions Core/Dialog/PSPSaveDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "Core/Dialog/PSPSaveDialog.h"
#include "Core/FileSystems/MetaFileSystem.h"
#include "Core/Util/PPGeDraw.h"
#include "Common/TimeUtil.h"
#include "Core/HLE/sceCtrl.h"
#include "Core/HLE/sceUtility.h"
#include "Core/HLE/ErrorCodes.h"
Expand All @@ -38,6 +39,20 @@
#include "Core/Reporting.h"
#include "Core/SaveState.h"

static double g_lastSaveTime = -1.0;

void ResetSecondsSinceLastGameSave() {
g_lastSaveTime = time_now_d();
}

double SecondsSinceLastGameSave() {
if (g_lastSaveTime < 0) {
return -1.0;
} else {
return time_now_d() - g_lastSaveTime;
}
}

const static float FONT_SCALE = 0.55f;

// These are rough, it seems to take at least 100ms or so to init, and shutdown depends on threads.
Expand Down Expand Up @@ -85,8 +100,7 @@ PSPSaveDialog::~PSPSaveDialog() {
JoinIOThread();
}

int PSPSaveDialog::Init(int paramAddr)
{
int PSPSaveDialog::Init(int paramAddr) {
// Ignore if already running
if (GetStatus() != SCE_UTILITY_STATUS_NONE) {
ERROR_LOG_REPORT(Log::sceUtility, "A save request is already running, not starting a new one");
Expand Down Expand Up @@ -1068,6 +1082,7 @@ void PSPSaveDialog::ExecuteIOAction() {
result = param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave);
if (result == 0) {
display = DS_LOAD_DONE;
g_lastSaveTime = time_now_d();
} else {
display = DS_LOAD_FAILED;
}
Expand All @@ -1076,6 +1091,7 @@ void PSPSaveDialog::ExecuteIOAction() {
SaveState::NotifySaveData();
if (param.Save(param.GetPspParam(), GetSelectedSaveDirName()) == 0) {
display = DS_SAVE_DONE;
g_lastSaveTime = time_now_d();
} else {
display = DS_SAVE_FAILED;
}
Expand Down
4 changes: 3 additions & 1 deletion Core/Dialog/PSPSaveDialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ class PSPSaveDialog : public PSPDialog {

std::thread *ioThread = nullptr;
std::mutex paramLock;
volatile SaveIOStatus ioThreadStatus;
volatile SaveIOStatus ioThreadStatus = SAVEIO_NONE;
};

void ResetSecondsSinceLastGameSave();
double SecondsSinceLastGameSave();
2 changes: 2 additions & 0 deletions Core/HLE/sceUtility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ void __UtilityInit() {
SavedataParam::Init();
currentlyLoadedModules.clear();
volatileUnlockEvent = CoreTiming::RegisterEvent("UtilityVolatileUnlock", UtilityVolatileUnlock);

ResetSecondsSinceLastGameSave();
}

void __UtilityDoState(PointerWrap &p) {
Expand Down
25 changes: 18 additions & 7 deletions Core/SaveState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@
// Slot number is visual only, -2 will display special message
constexpr int LOAD_UNDO_SLOT = -2;

namespace SaveState
{
namespace SaveState {

double g_lastSaveTime = -1.0;

struct SaveStart
{
void DoState(PointerWrap &p);
Expand All @@ -76,13 +78,10 @@ namespace SaveState
SAVESTATE_SAVE_SCREENSHOT,
};

struct Operation
{
struct Operation {
// The slot number is for visual purposes only. Set to -1 for operations where we don't display a message for example.
Operation(OperationType t, const Path &f, int slot_, Callback cb, void *cbUserData_)
: type(t), filename(f), callback(cb), slot(slot_), cbUserData(cbUserData_)
{
}
: type(t), filename(f), callback(cb), slot(slot_), cbUserData(cbUserData_) {}

OperationType type;
Path filename;
Expand Down Expand Up @@ -1005,6 +1004,7 @@ namespace SaveState
}
}
#endif
g_lastSaveTime = time_now_d();
} else if (result == CChunkFileReader::ERROR_BROKEN_STATE) {
HandleLoadFailure(false);
callbackMessage = std::string(i18nLoadFailure) + ": " + errorString;
Expand Down Expand Up @@ -1040,6 +1040,7 @@ namespace SaveState
}
}
#endif
g_lastSaveTime = time_now_d();
} else if (result == CChunkFileReader::ERROR_BROKEN_STATE) {
// TODO: What else might we want to do here? This should be very unusual.
callbackMessage = i18nSaveFailure;
Expand Down Expand Up @@ -1155,11 +1156,21 @@ namespace SaveState
saveDataGeneration = 0;
lastSaveDataGeneration = 0;
saveStateInitialGitVersion.clear();

g_lastSaveTime = time_now_d();
}

void Shutdown()
{
std::lock_guard<std::mutex> guard(mutex);
rewindStates.Clear();
}

double SecondsSinceLastSavestate() {
if (g_lastSaveTime < 0) {
return -1.0;
} else {
return time_now_d() - g_lastSaveTime;
}
}
}
8 changes: 5 additions & 3 deletions Core/SaveState.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
#include "Common/File/Path.h"
#include "Common/Serialize/Serializer.h"

namespace SaveState
{
namespace SaveState {
enum class Status {
FAILURE,
WARNING,
Expand Down Expand Up @@ -111,4 +110,7 @@ namespace SaveState

// Cleanup by triggering a restart if needed.
void Cleanup();
};

// Returns the time since last save. -1 if N/A.
double SecondsSinceLastSavestate();
} // namespace SaveState
3 changes: 3 additions & 0 deletions UI/GameSettingsScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,9 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) {

systemSettings->Add(new ItemHeader(sy->T("General")));

PopupSliderChoice *exitConfirmation = systemSettings->Add(new PopupSliderChoice(&g_Config.iAskForExitConfirmationAfterSeconds, 0, 1200, 60, sy->T("Ask for exit confirmation after seconds"), screenManager(), "s"));
exitConfirmation->SetZeroLabel(sy->T("Off"));

#if PPSSPP_PLATFORM(ANDROID)
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) {
auto co = GetI18NCategory(I18NCat::CONTROLS);
Expand Down
32 changes: 28 additions & 4 deletions UI/PauseScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "Common/GPU/thin3d.h"

#include "Common/Data/Text/I18n.h"
#include "Common/Data/Text/Parsers.h"
#include "Common/StringUtils.h"
#include "Common/System/OSD.h"
#include "Common/System/Request.h"
Expand All @@ -34,6 +35,7 @@

#include "Core/KeyMap.h"
#include "Core/Reporting.h"
#include "Core/Dialog/PSPSaveDialog.h"
#include "Core/SaveState.h"
#include "Core/System.h"
#include "Core/Core.h"
Expand Down Expand Up @@ -516,9 +518,9 @@ void GamePauseScreen::CreateViews() {
rightColumnItems->Add(new Spacer(20.0));
if (g_Config.bPauseMenuExitsEmulator) {
auto mm = GetI18NCategory(I18NCat::MAINMENU);
rightColumnItems->Add(new Choice(mm->T("Exit")))->OnClick.Handle(this, &GamePauseScreen::OnExitToMenu);
rightColumnItems->Add(new Choice(mm->T("Exit")))->OnClick.Handle(this, &GamePauseScreen::OnExit);
} else {
rightColumnItems->Add(new Choice(pa->T("Exit to menu")))->OnClick.Handle(this, &GamePauseScreen::OnExitToMenu);
rightColumnItems->Add(new Choice(pa->T("Exit to menu")))->OnClick.Handle(this, &GamePauseScreen::OnExit);
}

middleColumn->SetSpacing(20.0f);
Expand Down Expand Up @@ -578,12 +580,34 @@ UI::EventReturn GamePauseScreen::OnScreenshotClicked(UI::EventParams &e) {
return UI::EVENT_DONE;
}

UI::EventReturn GamePauseScreen::OnExitToMenu(UI::EventParams &e) {
int GetUnsavedProgressSeconds() {
const double timeSinceSaveState = SaveState::SecondsSinceLastSavestate();
const double timeSinceGameSave = SecondsSinceLastGameSave();

return (int)std::min(timeSinceSaveState, timeSinceGameSave);
}

UI::EventReturn GamePauseScreen::OnExit(UI::EventParams &e) {
std::string confirmMessage;

int unsavedSeconds = GetUnsavedProgressSeconds();

// If RAIntegration has dirty info, ask for confirmation.
if (Achievements::RAIntegrationDirty()) {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
confirmMessage = ac->T("You have unsaved RAIntegration changes. Exit?");
} else if (g_Config.iAskForExitConfirmationAfterSeconds > 0 && unsavedSeconds > g_Config.iAskForExitConfirmationAfterSeconds) {
auto di = GetI18NCategory(I18NCat::DIALOG);
std::string dlgMsg = ApplySafeSubstitutions(di->T("You haven't saved your progress for %1."), NiceTimeFormat((int)unsavedSeconds));
dlgMsg += '\n';
dlgMsg += '\n';
dlgMsg += di->T("Are you sure you want to exit? All unsaved progress will be lost.");
confirmMessage = dlgMsg;
}

if (!confirmMessage.empty()) {
auto di = GetI18NCategory(I18NCat::DIALOG);
screenManager()->push(new PromptScreen(gamePath_, ac->T("You have unsaved RAIntegration changes. Exit?"), di->T("Yes"), di->T("No"), [=](bool result) {
screenManager()->push(new PromptScreen(gamePath_, confirmMessage, di->T("Yes"), di->T("No"), [=](bool result) {
if (result) {
if (g_Config.bPauseMenuExitsEmulator) {
System_ExitApp();
Expand Down
2 changes: 1 addition & 1 deletion UI/PauseScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class GamePauseScreen : public UIDialogScreenWithGameBackground {
void CreateSavestateControls(UI::LinearLayout *viewGroup, bool vertical);

UI::EventReturn OnGameSettings(UI::EventParams &e);
UI::EventReturn OnExitToMenu(UI::EventParams &e);
UI::EventReturn OnExit(UI::EventParams &e);
UI::EventReturn OnReportFeedback(UI::EventParams &e);

UI::EventReturn OnRewind(UI::EventParams &e);
Expand Down
37 changes: 34 additions & 3 deletions Windows/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "Common/TimeUtil.h"
#include "Common/StringUtils.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Data/Text/Parsers.h"
#include "Common/Input/InputState.h"
#include "Common/Input/KeyCodes.h"
#include "Common/Thread/ThreadUtil.h"
Expand Down Expand Up @@ -85,6 +86,8 @@
#include "GPU/GPUCommon.h"
#include "UI/OnScreenDisplay.h"
#include "UI/GameSettingsScreen.h"
#include "Core/SaveState.h"
#include "Core/Dialog/PSPSaveDialog.h"

#define MOUSEEVENTF_FROMTOUCH_NOPEN 0xFF515780 //http://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx
#define MOUSEEVENTF_MASK_PLUS_PENTOUCH 0xFFFFFF80
Expand Down Expand Up @@ -813,6 +816,29 @@ namespace MainWindow
};
}

bool ConfirmExit(HWND hWnd) {
GlobalUIState state = GetUIState();
if (state != UISTATE_MENU && state != UISTATE_EXIT && g_Config.iAskForExitConfirmationAfterSeconds > 0) {
const double timeSinceSaveState = SaveState::SecondsSinceLastSavestate();
const double timeSinceGameSave = SecondsSinceLastGameSave();

const double minTime = std::min(timeSinceSaveState, timeSinceGameSave);
if (minTime > g_Config.iAskForExitConfirmationAfterSeconds) {
auto di = GetI18NCategory(I18NCat::DIALOG);
auto mm = GetI18NCategory(I18NCat::MAINMENU);
std::string dlgMsg = ApplySafeSubstitutions(di->T("You haven't saved your progress for %1."), NiceTimeFormat((int)minTime));
dlgMsg += '\n';
dlgMsg += '\n';
dlgMsg += di->T("Are you sure you want to exit? All unsaved progress will be lost.");

if (IDNO == MessageBox(hWnd, ConvertUTF8ToWString(dlgMsg).c_str(), ConvertUTF8ToWString(mm->T("Exit")).c_str(), MB_YESNO | MB_ICONQUESTION)) {
return false;
}
}
}
return true;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
LRESULT darkResult = 0;
if (UAHDarkModeWndProc(hWnd, message, wParam, lParam, &darkResult)) {
Expand Down Expand Up @@ -1090,12 +1116,17 @@ namespace MainWindow
break;

case WM_CLOSE:
{
if (ConfirmExit(hWnd)) {
DestroyWindow(hWnd);
}
return 0;
}

case WM_DESTROY:
InputDevice::StopPolling();
MainThread_Stop();
WindowsRawInput::Shutdown();
return DefWindowProc(hWnd,message,wParam,lParam);

case WM_DESTROY:
KillTimer(hWnd, TIMER_CURSORUPDATE);
KillTimer(hWnd, TIMER_CURSORMOVEUPDATE);
// Main window is gone, this tells the message loop to exit.
Expand Down
1 change: 1 addition & 0 deletions Windows/MainWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ namespace MainWindow
void SetWindowSize(int zoom);
void RunCallbackInWndProc(void (*callback)(void *window, void *userdata), void *userdata);
void SetKeepScreenBright(bool keepBright);
bool ConfirmExit(HWND hWnd);
}

#endif
6 changes: 4 additions & 2 deletions Windows/MainWindowMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ namespace MainWindow {
PostMessage(MainWindow::GetHWND(), WM_USER_RESTART_EMUTHREAD, 0, 0);
} else {
g_Config.bRestartRequired = true;
PostMessage(MainWindow::GetHWND(), WM_CLOSE, 0, 0);
DestroyWindow(MainWindow::GetHWND());
}
}

Expand Down Expand Up @@ -755,7 +755,9 @@ namespace MainWindow {
case ID_OPTIONS_FRAMESKIPTYPE_PRCNT: setFrameSkippingType(FRAMESKIPTYPE_PRCNT); break;

case ID_FILE_EXIT:
PostMessage(hWnd, WM_CLOSE, 0, 0);
if (MainWindow::ConfirmExit(hWnd)) {
DestroyWindow(hWnd);
}
break;

case ID_DEBUG_BREAKONLOAD:
Expand Down
Loading
Loading