CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/Dialog/PSPSaveDialog.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#ifdef __MINGW32__18#include <unistd.h>19#ifndef _POSIX_THREAD_SAFE_FUNCTIONS20#define _POSIX_THREAD_SAFE_FUNCTIONS 200112L21#endif22#endif2324#include <algorithm>25#include <ctime>26#include <thread>2728#include "Common/Data/Encoding/Utf8.h"29#include "Common/Data/Text/I18n.h"30#include "Common/File/FileUtil.h"31#include "Common/Serialize/Serializer.h"32#include "Common/Serialize/SerializeFuncs.h"33#include "Common/StringUtils.h"34#include "Common/Thread/ThreadUtil.h"35#include "Core/Dialog/PSPSaveDialog.h"36#include "Core/FileSystems/MetaFileSystem.h"37#include "Core/Util/PPGeDraw.h"38#include "Core/HLE/sceCtrl.h"39#include "Core/HLE/sceUtility.h"40#include "Core/HW/MemoryStick.h"41#include "Core/MemMapHelpers.h"42#include "Core/Config.h"43#include "Core/Reporting.h"44#include "Core/SaveState.h"4546const static float FONT_SCALE = 0.55f;4748// These are rough, it seems to take at least 100ms or so to init, and shutdown depends on threads.49// Some games seem to required slightly longer delays to work, so we try 200ms as a compromise.50const static int SAVEDATA_INIT_DELAY_US = 200000;51const static int SAVEDATA_SHUTDOWN_DELAY_US = 2000;5253// These are the only sizes which are allowed.54// TODO: We should test what the different behavior is for each.55const static int SAVEDATA_DIALOG_SIZE_V1 = 1480;56const static int SAVEDATA_DIALOG_SIZE_V2 = 1500;57const static int SAVEDATA_DIALOG_SIZE_V3 = 1536;5859static bool IsNotVisibleAction(SceUtilitySavedataType type) {60switch (type) {61case SCE_UTILITY_SAVEDATA_TYPE_AUTOLOAD:62case SCE_UTILITY_SAVEDATA_TYPE_AUTOSAVE:63case SCE_UTILITY_SAVEDATA_TYPE_SIZES:64case SCE_UTILITY_SAVEDATA_TYPE_LIST:65case SCE_UTILITY_SAVEDATA_TYPE_FILES:66case SCE_UTILITY_SAVEDATA_TYPE_GETSIZE:67case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE:68case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATA:69case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE:70case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA:71case SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE:72case SCE_UTILITY_SAVEDATA_TYPE_READDATA:73case SCE_UTILITY_SAVEDATA_TYPE_ERASESECURE:74case SCE_UTILITY_SAVEDATA_TYPE_ERASE:75case SCE_UTILITY_SAVEDATA_TYPE_DELETEDATA:76case SCE_UTILITY_SAVEDATA_TYPE_AUTODELETE:77return true;7879default:80break;81}82return false;83}8485PSPSaveDialog::PSPSaveDialog(UtilityDialogType type) : PSPDialog(type) {86param.SetPspParam(0);87}8889PSPSaveDialog::~PSPSaveDialog() {90JoinIOThread();91}9293int PSPSaveDialog::Init(int paramAddr)94{95// Ignore if already running96if (GetStatus() != SCE_UTILITY_STATUS_NONE) {97ERROR_LOG_REPORT(Log::sceUtility, "A save request is already running, not starting a new one");98return SCE_ERROR_UTILITY_INVALID_STATUS;99}100101JoinIOThread();102ioThreadStatus = SAVEIO_NONE;103104requestAddr = paramAddr;105int size = Memory::Read_U32(requestAddr);106memset(&request, 0, sizeof(request));107// Only copy the right size to support different save request format108if (size != SAVEDATA_DIALOG_SIZE_V1 && size != SAVEDATA_DIALOG_SIZE_V2 && size != SAVEDATA_DIALOG_SIZE_V3) {109ERROR_LOG_REPORT(Log::sceUtility, "sceUtilitySavedataInitStart: invalid size %d", size);110return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE;111}112Memory::Memcpy(&request, requestAddr, size);113Memory::Memcpy(&originalRequest, requestAddr, size);114115param.SetIgnoreTextures(IsNotVisibleAction((SceUtilitySavedataType)(u32)request.mode));116param.ClearSFOCache();117int retval = param.SetPspParam(&request);118119const u32 mode = (u32)param.GetPspParam()->mode;120const char *modeName = mode < ARRAY_SIZE(utilitySavedataTypeNames) ? utilitySavedataTypeNames[mode] : "UNKNOWN";121INFO_LOG(Log::sceUtility,"sceUtilitySavedataInitStart(%08x) - %s (%d)", paramAddr, modeName, mode);122INFO_LOG(Log::sceUtility,"sceUtilitySavedataInitStart(%08x) : Game key (hex): %s", paramAddr, param.GetKey(param.GetPspParam()).c_str());123124yesnoChoice = 1;125switch ((SceUtilitySavedataFocus)(u32)param.GetPspParam()->focus)126{127case SCE_UTILITY_SAVEDATA_FOCUS_NAME:128currentSelectedSave = param.GetSaveNameIndex(param.GetPspParam());129break;130case SCE_UTILITY_SAVEDATA_FOCUS_FIRSTLIST:131currentSelectedSave = param.GetFirstListSave();132break;133case SCE_UTILITY_SAVEDATA_FOCUS_LASTLIST:134currentSelectedSave = param.GetLastListSave();135break;136case SCE_UTILITY_SAVEDATA_FOCUS_LATEST:137currentSelectedSave = param.GetLatestSave();138break;139case SCE_UTILITY_SAVEDATA_FOCUS_OLDEST:140currentSelectedSave = param.GetOldestSave();141break;142case SCE_UTILITY_SAVEDATA_FOCUS_FIRSTDATA:143currentSelectedSave = param.GetFirstDataSave();144break;145case SCE_UTILITY_SAVEDATA_FOCUS_LASTDATA:146currentSelectedSave = param.GetLastDataSave();147break;148case SCE_UTILITY_SAVEDATA_FOCUS_FIRSTEMPTY:149currentSelectedSave = param.GetFirstEmptySave();150break;151case SCE_UTILITY_SAVEDATA_FOCUS_LASTEMPTY:152currentSelectedSave = param.GetLastEmptySave();153break;154default:155WARN_LOG(Log::sceUtility, "Unknown save list focus option: %d", param.GetPspParam()->focus);156currentSelectedSave = 0;157break;158}159160if (!param.WouldHaveMultiSaveName(param.GetPspParam()))161currentSelectedSave = 0;162163switch ((SceUtilitySavedataType)(u32)param.GetPspParam()->mode)164{165case SCE_UTILITY_SAVEDATA_TYPE_LOAD:166DEBUG_LOG(Log::sceUtility, "Loading. Title: %s Save: %s File: %s", param.GetGameName(param.GetPspParam()).c_str(), param.GetSaveName(param.GetPspParam()).c_str(), param.GetFileName(param.GetPspParam()).c_str());167if (param.GetFileInfo(0).size != 0) {168if (param.GetFileInfo(0).broken) {169param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN;170display = DS_LOAD_FAILED;171} else {172display = DS_LOAD_CONFIRM;173}174} else175display = DS_LOAD_NODATA;176break;177case SCE_UTILITY_SAVEDATA_TYPE_AUTOLOAD:178DEBUG_LOG(Log::sceUtility, "Loading. Title: %s Save: %s File: %s", param.GetGameName(param.GetPspParam()).c_str(), param.GetSaveName(param.GetPspParam()).c_str(), param.GetFileName(param.GetPspParam()).c_str());179display = DS_NONE;180// Is this necessary?181// currentSelectedSave = param.GetSelectedSave();182break;183case SCE_UTILITY_SAVEDATA_TYPE_LISTLOAD:184DEBUG_LOG(Log::sceUtility, "Loading. Title: %s Save: %s File: %s", param.GetGameName(param.GetPspParam()).c_str(), param.GetGameName(param.GetPspParam()).c_str(), param.GetFileName(param.GetPspParam()).c_str());185if(param.GetFilenameCount() == 0)186display = DS_LOAD_NODATA;187else188display = DS_LOAD_LIST_CHOICE;189break;190case SCE_UTILITY_SAVEDATA_TYPE_SAVE:191DEBUG_LOG(Log::sceUtility, "Saving. Title: %s Save: %s File: %s", param.GetGameName(param.GetPspParam()).c_str(), param.GetGameName(param.GetPspParam()).c_str(), param.GetFileName(param.GetPspParam()).c_str());192if (param.GetFileInfo(0).size != 0)193{194yesnoChoice = 0;195display = DS_SAVE_CONFIRM_OVERWRITE;196}197else198display = DS_SAVE_CONFIRM;199break;200case SCE_UTILITY_SAVEDATA_TYPE_AUTOSAVE:201DEBUG_LOG(Log::sceUtility, "Saving. Title: %s Save: %s File: %s", param.GetGameName(param.GetPspParam()).c_str(), param.GetGameName(param.GetPspParam()).c_str(), param.GetFileName(param.GetPspParam()).c_str());202display = DS_NONE;203// Is this necessary?204// currentSelectedSave = param.GetSelectedSave();205break;206case SCE_UTILITY_SAVEDATA_TYPE_LISTSAVE:207DEBUG_LOG(Log::sceUtility, "Saving. Title: %s Save: %s File: %s", param.GetGameName(param.GetPspParam()).c_str(), param.GetGameName(param.GetPspParam()).c_str(), param.GetFileName(param.GetPspParam()).c_str());208display = DS_SAVE_LIST_CHOICE;209break;210case SCE_UTILITY_SAVEDATA_TYPE_LISTALLDELETE:211DEBUG_LOG(Log::sceUtility, "Delete. Title: %s Save: %s File: %s", param.GetGameName(param.GetPspParam()).c_str(), param.GetGameName(param.GetPspParam()).c_str(), param.GetFileName(param.GetPspParam()).c_str());212if(param.GetFilenameCount() == 0)213display = DS_DELETE_NODATA;214else215display = DS_DELETE_LIST_CHOICE;216break;217case SCE_UTILITY_SAVEDATA_TYPE_SIZES:218case SCE_UTILITY_SAVEDATA_TYPE_LIST:219case SCE_UTILITY_SAVEDATA_TYPE_FILES:220case SCE_UTILITY_SAVEDATA_TYPE_GETSIZE:221case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE:222case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATA:223case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE:224case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA:225case SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE:226case SCE_UTILITY_SAVEDATA_TYPE_READDATA:227case SCE_UTILITY_SAVEDATA_TYPE_ERASESECURE:228case SCE_UTILITY_SAVEDATA_TYPE_ERASE:229case SCE_UTILITY_SAVEDATA_TYPE_DELETEDATA:230display = DS_NONE;231break;232233case SCE_UTILITY_SAVEDATA_TYPE_DELETE:234DEBUG_LOG(Log::sceUtility, "Delete. Title: %s Save: %s File: %s", param.GetGameName(param.GetPspParam()).c_str(), param.GetGameName(param.GetPspParam()).c_str(), param.GetFileName(param.GetPspParam()).c_str());235if (param.GetFileInfo(0).size != 0) {236yesnoChoice = 0;237display = DS_DELETE_CONFIRM;238} else239display = DS_DELETE_NODATA;240break;241242case SCE_UTILITY_SAVEDATA_TYPE_AUTODELETE:243DEBUG_LOG(Log::sceUtility, "Delete. Title: %s Save: %s File: %s", param.GetGameName(param.GetPspParam()).c_str(), param.GetGameName(param.GetPspParam()).c_str(), param.GetFileName(param.GetPspParam()).c_str());244display = DS_NONE;245break;246247case SCE_UTILITY_SAVEDATA_TYPE_LISTDELETE:248DEBUG_LOG(Log::sceUtility, "Delete. Title: %s Save: %s File: %s", param.GetGameName(param.GetPspParam()).c_str(), param.GetGameName(param.GetPspParam()).c_str(), param.GetFileName(param.GetPspParam()).c_str());249if (param.GetFilenameCount() == 0)250display = DS_DELETE_NODATA;251else252display = DS_DELETE_LIST_CHOICE;253break;254default:255{256ERROR_LOG_REPORT(Log::sceUtility, "Load/Save function %d not coded. Title: %s Save: %s File: %s", (SceUtilitySavedataType)(u32)param.GetPspParam()->mode, param.GetGameName(param.GetPspParam()).c_str(), param.GetGameName(param.GetPspParam()).c_str(), param.GetFileName(param.GetPspParam()).c_str());257param.GetPspParam()->common.result = 0;258ChangeStatusInit(SAVEDATA_INIT_DELAY_US);259display = DS_NONE;260return 0; // Return 0 should allow the game to continue, but missing function must be implemented and returning the right value or the game can block.261}262break;263}264265if (retval < 0) {266ChangeStatusShutdown(SAVEDATA_SHUTDOWN_DELAY_US);267} else {268ChangeStatusInit(SAVEDATA_INIT_DELAY_US);269}270271param.ClearSFOCache();272InitCommon();273UpdateButtons();274StartFade(true);275276/*INFO_LOG(Log::sceUtility,"Dump Param :");277INFO_LOG(Log::sceUtility,"size : %d",param.GetPspParam()->common.size);278INFO_LOG(Log::sceUtility,"language : %d",param.GetPspParam()->common.language);279INFO_LOG(Log::sceUtility,"buttonSwap : %d",param.GetPspParam()->common.buttonSwap);280INFO_LOG(Log::sceUtility,"result : %d",param.GetPspParam()->common.result);281INFO_LOG(Log::sceUtility,"mode : %d",param.GetPspParam()->mode);282INFO_LOG(Log::sceUtility,"bind : %d",param.GetPspParam()->bind);283INFO_LOG(Log::sceUtility,"overwriteMode : %d",param.GetPspParam()->overwriteMode);284INFO_LOG(Log::sceUtility,"gameName : %s",param.GetGameName(param.GetPspParam()).c_str());285INFO_LOG(Log::sceUtility,"saveName : %s",param.GetPspParam()->saveName);286INFO_LOG(Log::sceUtility,"saveNameList : %08x",*((unsigned int*)¶m.GetPspParam()->saveNameList));287INFO_LOG(Log::sceUtility,"fileName : %s",param.GetPspParam()->fileName);288INFO_LOG(Log::sceUtility,"dataBuf : %08x",*((unsigned int*)¶m.GetPspParam()->dataBuf));289INFO_LOG(Log::sceUtility,"dataBufSize : %u",param.GetPspParam()->dataBufSize);290INFO_LOG(Log::sceUtility,"dataSize : %u",param.GetPspParam()->dataSize);291292INFO_LOG(Log::sceUtility,"sfo title : %s",param.GetPspParam()->sfoParam.title);293INFO_LOG(Log::sceUtility,"sfo savedataTitle : %s",param.GetPspParam()->sfoParam.savedataTitle);294INFO_LOG(Log::sceUtility,"sfo detail : %s",param.GetPspParam()->sfoParam.detail);295296INFO_LOG(Log::sceUtility,"icon0 data : %08x",*((unsigned int*)¶m.GetPspParam()->icon0FileData.buf));297INFO_LOG(Log::sceUtility,"icon0 size : %u",param.GetPspParam()->icon0FileData.bufSize);298299INFO_LOG(Log::sceUtility,"icon1 data : %08x",*((unsigned int*)¶m.GetPspParam()->icon1FileData.buf));300INFO_LOG(Log::sceUtility,"icon1 size : %u",param.GetPspParam()->icon1FileData.bufSize);301302INFO_LOG(Log::sceUtility,"pic1 data : %08x",*((unsigned int*)¶m.GetPspParam()->pic1FileData.buf));303INFO_LOG(Log::sceUtility,"pic1 size : %u",param.GetPspParam()->pic1FileData.bufSize);304305INFO_LOG(Log::sceUtility,"snd0 data : %08x",*((unsigned int*)¶m.GetPspParam()->snd0FileData.buf));306INFO_LOG(Log::sceUtility,"snd0 size : %u",param.GetPspParam()->snd0FileData.bufSize);*/307INFO_LOG(Log::sceUtility, "Return value: %d", retval);308return retval;309}310311std::string PSPSaveDialog::GetSelectedSaveDirName() const312{313switch ((SceUtilitySavedataType)(u32)param.GetPspParam()->mode)314{315case SCE_UTILITY_SAVEDATA_TYPE_LOAD:316case SCE_UTILITY_SAVEDATA_TYPE_AUTOLOAD:317case SCE_UTILITY_SAVEDATA_TYPE_SAVE:318case SCE_UTILITY_SAVEDATA_TYPE_AUTOSAVE:319case SCE_UTILITY_SAVEDATA_TYPE_AUTODELETE:320case SCE_UTILITY_SAVEDATA_TYPE_DELETE:321return param.GetSaveDirName(param.GetPspParam());322323case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE:324case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATA:325case SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE:326case SCE_UTILITY_SAVEDATA_TYPE_READDATA:327case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE:328case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA:329case SCE_UTILITY_SAVEDATA_TYPE_ERASESECURE:330case SCE_UTILITY_SAVEDATA_TYPE_ERASE:331case SCE_UTILITY_SAVEDATA_TYPE_DELETEDATA:332return param.GetSaveDirName(param.GetPspParam());333334// SIZES ignores saveName it seems.335336default:337return param.GetSaveDirName(param.GetPspParam(), currentSelectedSave);338break;339}340}341342void PSPSaveDialog::DisplayBanner(int which)343{344auto di = GetI18NCategory(I18NCat::DIALOG);345PPGeDrawRect(0, 0, 480, 23, CalcFadedColor(0x65636358));346347PPGeStyle textStyle = FadedStyle(PPGeAlign::BOX_VCENTER, 0.6f);348textStyle.hasShadow = false;349350std::string_view title;351switch (which) {352case DB_SAVE:353title = di->T("Save");354break;355case DB_LOAD:356title = di->T("Load");357break;358case DB_DELETE:359title = di->T("Delete");360break;361default:362title = "";363break;364}365// TODO: Draw a hexagon icon366PPGeDrawImage(10, 6, 12.0f, 12.0f, 1, 10, 1, 10, 10, 10, FadedImageStyle());367PPGeDrawText(title, 30, 11, textStyle);368}369370void PSPSaveDialog::DisplaySaveList(bool canMove) {371std::lock_guard<std::mutex> guard(paramLock);372static int upFramesHeld = 0;373static int downFramesHeld = 0;374375for (int displayCount = 0; displayCount < param.GetFilenameCount(); displayCount++) {376PPGeImageStyle imageStyle = FadedImageStyle();377auto fileInfo = param.GetFileInfo(displayCount);378379if (fileInfo.size == 0 && fileInfo.texture && fileInfo.texture->IsValid())380imageStyle.color = CalcFadedColor(0xFF777777);381382// Calc save image position on screen383float w, h, x;384float y = 97;385if (displayCount != currentSelectedSave) {386w = 81;387h = 45;388x = 58.5f;389} else {390w = 144;391h = 80;392x = 27;393}394if (displayCount < currentSelectedSave)395y -= 13 + 45 * (currentSelectedSave - displayCount);396else if (displayCount > currentSelectedSave)397y += 48 + 45 * (displayCount - currentSelectedSave);398399// Skip if it's well outside the screen.400if (y > 472.0f || y < -200.0f)401continue;402403int pad = 0;404if (fileInfo.texture != nullptr && fileInfo.texture->IsValid()) {405fileInfo.texture->SetTexture();406int tw = fileInfo.texture->Width();407int th = fileInfo.texture->Height();408float scale = (float)h / (float)th;409int scaledW = (int)(tw * scale);410pad = (w - scaledW) / 2;411w = scaledW;412413PPGeDrawImage(x + pad, y, w, h, 0, 0, 1, 1, tw, th, imageStyle);414} else {415PPGeDrawRect(x, y, x + w, y + h, 0x88666666);416}417if (displayCount == currentSelectedSave) {418float b = 1.2f;419uint32_t bc = CalcFadedColor(0xD0FFFFFF);420PPGeDrawRect(x + pad - b, y - b, x + pad + w + b, y, bc); // top border421PPGeDrawRect(x + pad - b, y, x + pad, y + h, bc); // left border422PPGeDrawRect(x + pad - b, y + h, x + pad + w + b, y + h + b, bc); //bottom border423PPGeDrawRect(x + pad + w, y, x + pad + w + b, y + h, bc); //right border424}425PPGeSetDefaultTexture();426}427428if (canMove) {429if ( (IsButtonPressed(CTRL_UP) || IsButtonHeld(CTRL_UP, upFramesHeld)) && currentSelectedSave > 0)430currentSelectedSave--;431432else if ( (IsButtonPressed(CTRL_DOWN) || IsButtonHeld(CTRL_DOWN, downFramesHeld)) && currentSelectedSave < (param.GetFilenameCount() - 1))433currentSelectedSave++;434}435}436437void PSPSaveDialog::DisplaySaveIcon(bool checkExists)438{439std::lock_guard<std::mutex> guard(paramLock);440PPGeImageStyle imageStyle = FadedImageStyle();441auto curSave = param.GetFileInfo(currentSelectedSave);442443if (curSave.size == 0 && checkExists)444imageStyle.color = CalcFadedColor(0xFF777777);445446// Calc save image position on screen447float w = 144;448float h = 80;449float x = 27;450float y = 97;451452int tw = 256;453int th = 256;454if (curSave.texture != nullptr && curSave.texture->IsValid()) {455curSave.texture->SetTexture();456tw = curSave.texture->Width();457th = curSave.texture->Height();458float scale = (float)h / (float)th;459int scaledW = (int)(tw * scale);460x += (w - scaledW) / 2;461w = scaledW;462} else {463PPGeDisableTexture();464}465PPGeDrawImage(x, y, w, h, 0, 0, 1, 1, tw, th, imageStyle);466PPGeSetDefaultTexture();467}468469static void FormatSaveHourMin(char *hour_time, size_t sz, const tm &t) {470const char *am_pm = "AM";471int hour = t.tm_hour;472switch (g_Config.iTimeFormat) {473case 1:474if (hour == 12) {475am_pm = "PM";476} else if (hour > 12) {477am_pm = "PM";478hour -= 12;479} else if (hour == 0) {480hour = 12;481}482snprintf(hour_time, sz, "%02d:%02d %s", hour, t.tm_min, am_pm);483break;484case 0:485default:486snprintf(hour_time, sz, "%02d:%02d", hour, t.tm_min);487break;488}489}490491static void FormatSaveDate(char *date, size_t sz, const tm &t) {492int year = t.tm_year + 1900;493int month = t.tm_mon + 1;494switch (g_Config.iDateFormat) {495case 1:496snprintf(date, sz, "%02d/%02d/%04d", month, t.tm_mday, year);497break;498case 2:499snprintf(date, sz, "%02d/%02d/%04d", t.tm_mday, month, year);500break;501case 0:502default:503snprintf(date, sz, "%04d/%02d/%02d", year, month, t.tm_mday);504break;505}506}507508void PSPSaveDialog::DisplaySaveDataInfo1() {509std::lock_guard<std::mutex> guard(paramLock);510const SaveFileInfo &saveInfo = param.GetFileInfo(currentSelectedSave);511PPGeStyle saveTitleStyle = FadedStyle(PPGeAlign::BOX_LEFT, 0.55f);512513if (saveInfo.broken) {514auto di = GetI18NCategory(I18NCat::DIALOG);515PPGeStyle textStyle = FadedStyle(PPGeAlign::BOX_VCENTER, 0.6f);516PPGeDrawText(di->T("Corrupted Data"), 180, 136, textStyle);517PPGeDrawText(saveInfo.title, 175, 159, saveTitleStyle);518} else if (saveInfo.size == 0) {519auto di = GetI18NCategory(I18NCat::DIALOG);520PPGeStyle textStyle = FadedStyle(PPGeAlign::BOX_VCENTER, 0.6f);521PPGeDrawText(di->T("NEW DATA"), 180, 136, textStyle);522} else {523char hour_time[32];524FormatSaveHourMin(hour_time, sizeof(hour_time), saveInfo.modif_time);525526char date_year[32];527FormatSaveDate(date_year, sizeof(date_year), saveInfo.modif_time);528529s64 sizeK = saveInfo.size / 1024;530531PPGeDrawRect(180, 136, 480, 137, CalcFadedColor(0xFFFFFFFF));532std::string titleTxt = saveInfo.title;533std::string timeTxt = StringFromFormat("%s %s %lld KB", date_year, hour_time, sizeK);534std::string saveTitleTxt = saveInfo.saveTitle;535std::string saveDetailTxt = saveInfo.saveDetail;536537PPGeStyle titleStyle = FadedStyle(PPGeAlign::BOX_BOTTOM, 0.6f);538titleStyle.color = CalcFadedColor(0xFFC0C0C0);539PPGeStyle textStyle = FadedStyle(PPGeAlign::BOX_LEFT, 0.5f);540541PPGeDrawText(titleTxt.c_str(), 180, 136, titleStyle);542PPGeDrawText(timeTxt.c_str(), 180, 137, textStyle);543PPGeDrawText(saveTitleTxt.c_str(), 175, 159, saveTitleStyle);544PPGeDrawTextWrapped(saveDetailTxt.c_str(), 175, 181, 480 - 175, 250 - 181, textStyle);545}546}547548void PSPSaveDialog::DisplaySaveDataInfo2(bool showNewData) {549std::lock_guard<std::mutex> guard(paramLock);550551tm modif_time;552const char *save_title;553u32 data_size;554555if (showNewData || param.GetFileInfo(currentSelectedSave).size == 0) {556time_t t;557time(&t);558localtime_r(&t, &modif_time);559save_title = param.GetPspParam()->sfoParam.savedataTitle;560// TODO: Account for icon, etc., etc.561data_size = param.GetPspParam()->dataSize;562} else {563modif_time = param.GetFileInfo(currentSelectedSave).modif_time;564save_title = param.GetFileInfo(currentSelectedSave).saveTitle;565data_size = param.GetFileInfo(currentSelectedSave).size;566}567568char hour_time[32];569FormatSaveHourMin(hour_time, sizeof(hour_time), modif_time);570571char date_year[32];572FormatSaveDate(date_year, sizeof(date_year), modif_time);573574s64 sizeK = data_size / 1024;575576PPGeStyle textStyle = FadedStyle(PPGeAlign::BOX_LEFT, 0.5f);577std::string title = SanitizeUTF8(std::string(save_title, strnlen(save_title, 128)));578std::string saveinfoTxt = StringFromFormat("%s\n%s %s\n%lld KB", title.c_str(), date_year, hour_time, sizeK);579PPGeDrawText(saveinfoTxt.c_str(), 8, 200, textStyle);580}581582void PSPSaveDialog::DisplayMessage(std::string_view text, bool hasYesNo)583{584PPGeStyle textStyle = FadedStyle(PPGeAlign::BOX_CENTER, FONT_SCALE);585586const float WRAP_WIDTH = 254.0f;587float y = 136.0f, h;588PPGeMeasureText(nullptr, &h, text, FONT_SCALE, PPGE_LINE_WRAP_WORD, WRAP_WIDTH);589float h2 = h / 2.0f;590if (hasYesNo)591{592auto di = GetI18NCategory(I18NCat::DIALOG);593std::string_view choiceText;594float x, w;595if (yesnoChoice == 1) {596choiceText = di->T("Yes");597x = 302.0f;598}599else {600choiceText = di->T("No");601x = 366.0f;602}603PPGeMeasureText(&w, &h, choiceText, FONT_SCALE);604w = w / 2.0f + 5.5f;605h /= 2.0f;606float y2 = y + h2 + 4.0f;607h2 += h + 4.0f;608y = 132.0f - h;609PPGeDrawRect(x - w, y2 - h, x + w, y2 + h, CalcFadedColor(0x40C0C0C0));610PPGeDrawText(di->T("Yes"), 302.0f, y2, textStyle);611PPGeDrawText(di->T("No"), 366.0f, y2, textStyle);612if (IsButtonPressed(CTRL_LEFT) && yesnoChoice == 0) {613yesnoChoice = 1;614}615else if (IsButtonPressed(CTRL_RIGHT) && yesnoChoice == 1) {616yesnoChoice = 0;617}618}619PPGeDrawTextWrapped(text, 334.0f, y, WRAP_WIDTH, 0, textStyle);620float sy = 122.0f - h2, ey = 150.0f + h2;621PPGeDrawRect(202.0f, sy, 466.0f, sy + 1.0f, CalcFadedColor(0xFFFFFFFF));622PPGeDrawRect(202.0f, ey, 466.0f, ey + 1.0f, CalcFadedColor(0xFFFFFFFF));623}624625int PSPSaveDialog::Update(int animSpeed)626{627if (GetStatus() != SCE_UTILITY_STATUS_RUNNING)628return SCE_ERROR_UTILITY_INVALID_STATUS;629630if (!param.GetPspParam()) {631ChangeStatusShutdown(SAVEDATA_SHUTDOWN_DELAY_US);632return 0;633}634635if (pendingStatus != SCE_UTILITY_STATUS_RUNNING) {636// We're actually done, we're just waiting to tell the game that.637return 0;638}639640// The struct may have been updated by the game. This happens in "Where Is My Heart?"641// Check if it has changed, reload it.642// TODO: Cut down on preloading? This rebuilds the list from scratch.643int size = std::min((u32)sizeof(originalRequest), Memory::Read_U32(requestAddr));644const u8 *updatedRequest = Memory::GetPointerRange(requestAddr, size);645if (updatedRequest && memcmp(updatedRequest, &originalRequest, size) != 0) {646memset(&request, 0, sizeof(request));647Memory::Memcpy(&request, requestAddr, size);648Memory::Memcpy(&originalRequest, requestAddr, size);649std::lock_guard<std::mutex> guard(paramLock);650param.SetPspParam(&request);651}652653param.ClearSFOCache();654UpdateButtons();655UpdateFade(animSpeed);656657UpdateCommon();658659auto di = GetI18NCategory(I18NCat::DIALOG);660661switch (display)662{663case DS_SAVE_LIST_CHOICE:664StartDraw();665666DisplaySaveList();667DisplaySaveDataInfo1();668669DisplayButtons(DS_BUTTON_OK | DS_BUTTON_CANCEL);670DisplayBanner(DB_SAVE);671672if (IsButtonPressed(cancelButtonFlag)) {673param.GetPspParam()->common.result = SCE_UTILITY_DIALOG_RESULT_CANCEL;674StartFade(false);675} else if (IsButtonPressed(okButtonFlag)) {676// Save exist, ask user confirm677if (param.GetFileInfo(currentSelectedSave).size > 0) {678yesnoChoice = 0;679display = DS_SAVE_CONFIRM_OVERWRITE;680} else {681display = DS_SAVE_SAVING;682StartIOThread();683}684}685EndDraw();686break;687case DS_SAVE_CONFIRM:688StartDraw();689690DisplaySaveIcon(false);691DisplaySaveDataInfo2(true);692693DisplayMessage(di->T("Confirm Save", "Do you want to save this data?"), true);694695DisplayButtons(DS_BUTTON_OK | DS_BUTTON_CANCEL);696DisplayBanner(DB_SAVE);697698if (IsButtonPressed(cancelButtonFlag) || (IsButtonPressed(okButtonFlag) && yesnoChoice == 0)) {699param.GetPspParam()->common.result = SCE_UTILITY_DIALOG_RESULT_CANCEL;700StartFade(false);701} else if (IsButtonPressed(okButtonFlag)) {702display = DS_SAVE_SAVING;703StartIOThread();704}705706EndDraw();707break;708case DS_SAVE_CONFIRM_OVERWRITE:709StartDraw();710711DisplaySaveIcon(true);712DisplaySaveDataInfo2();713714DisplayMessage(di->T("Confirm Overwrite","Do you want to overwrite the data?"), true);715716DisplayButtons(DS_BUTTON_OK | DS_BUTTON_CANCEL);717DisplayBanner(DB_SAVE);718719if (IsButtonPressed(cancelButtonFlag) || (IsButtonPressed(okButtonFlag) && yesnoChoice == 0)) {720if (param.GetPspParam()->mode != SCE_UTILITY_SAVEDATA_TYPE_SAVE)721display = DS_SAVE_LIST_CHOICE;722else {723param.GetPspParam()->common.result = SCE_UTILITY_DIALOG_RESULT_CANCEL;724StartFade(false);725}726} else if (IsButtonPressed(okButtonFlag)) {727display = DS_SAVE_SAVING;728StartIOThread();729}730731EndDraw();732break;733case DS_SAVE_SAVING:734if (ioThreadStatus != SAVEIO_PENDING) {735JoinIOThread();736}737738StartDraw();739740DisplaySaveIcon(true);741DisplaySaveDataInfo2(true);742743DisplayMessage(di->T("Saving","Saving\nPlease Wait..."));744745DisplayBanner(DB_SAVE);746747EndDraw();748break;749case DS_SAVE_FAILED:750JoinIOThread();751StartDraw();752753DisplaySaveIcon(true);754DisplaySaveDataInfo2(true);755756DisplayMessage(di->T("SavingFailed", "Unable to save data."));757758DisplayButtons(DS_BUTTON_CANCEL);759DisplayBanner(DB_SAVE);760761if (IsButtonPressed(cancelButtonFlag)) {762// Go back to the list so they can try again.763if (param.GetPspParam()->mode != SCE_UTILITY_SAVEDATA_TYPE_SAVE) {764display = DS_SAVE_LIST_CHOICE;765} else {766param.GetPspParam()->common.result = SCE_UTILITY_DIALOG_RESULT_CANCEL;767StartFade(false);768}769}770771EndDraw();772break;773case DS_SAVE_DONE:774if (ioThread) {775JoinIOThread();776param.SetPspParam(param.GetPspParam());777}778StartDraw();779780DisplaySaveIcon(true);781DisplaySaveDataInfo2();782783DisplayMessage(di->T("Save completed"));784785DisplayButtons(DS_BUTTON_CANCEL);786DisplayBanner(DB_SAVE);787788if (IsButtonPressed(cancelButtonFlag)) {789param.GetPspParam()->common.result = SCE_UTILITY_DIALOG_RESULT_SUCCESS;790// Set the save to use for autosave and autoload791param.SetSelectedSave(param.GetFileInfo(currentSelectedSave).idx);792StartFade(false);793}794795EndDraw();796break;797798case DS_LOAD_LIST_CHOICE:799StartDraw();800801DisplaySaveList();802DisplaySaveDataInfo1();803804DisplayButtons(DS_BUTTON_OK | DS_BUTTON_CANCEL);805DisplayBanner(DB_LOAD);806807if (IsButtonPressed(cancelButtonFlag)) {808param.GetPspParam()->common.result = SCE_UTILITY_DIALOG_RESULT_CANCEL;809StartFade(false);810} else if (IsButtonPressed(okButtonFlag)) {811display = DS_LOAD_LOADING;812StartIOThread();813}814815EndDraw();816break;817case DS_LOAD_CONFIRM:818StartDraw();819820DisplaySaveIcon(true);821DisplaySaveDataInfo2();822823DisplayMessage(di->T("ConfirmLoad", "Load this data?"), true);824825DisplayButtons(DS_BUTTON_OK | DS_BUTTON_CANCEL);826DisplayBanner(DB_LOAD);827828if (IsButtonPressed(cancelButtonFlag) || (IsButtonPressed(okButtonFlag) && yesnoChoice == 0)) {829param.GetPspParam()->common.result = SCE_UTILITY_DIALOG_RESULT_CANCEL;830StartFade(false);831} else if (IsButtonPressed(okButtonFlag)) {832display = DS_LOAD_LOADING;833StartIOThread();834}835836EndDraw();837break;838case DS_LOAD_LOADING:839if (ioThreadStatus != SAVEIO_PENDING) {840JoinIOThread();841}842843StartDraw();844845DisplaySaveIcon(true);846DisplaySaveDataInfo2();847848DisplayMessage(di->T("Loading","Loading\nPlease Wait..."));849850DisplayBanner(DB_LOAD);851852EndDraw();853break;854case DS_LOAD_FAILED:855JoinIOThread();856StartDraw();857858DisplaySaveIcon(true);859DisplaySaveDataInfo2();860861DisplayMessage(di->T("LoadingFailed", "Load failed\nThe data is corrupted."));862863DisplayButtons(DS_BUTTON_CANCEL);864DisplayBanner(DB_LOAD);865866if (IsButtonPressed(cancelButtonFlag)) {867// Go back to the list so they can try again.868if (param.GetPspParam()->mode != SCE_UTILITY_SAVEDATA_TYPE_LOAD) {869display = DS_LOAD_LIST_CHOICE;870} else {871StartFade(false);872}873}874875EndDraw();876break;877case DS_LOAD_DONE:878JoinIOThread();879StartDraw();880881DisplaySaveIcon(true);882DisplaySaveDataInfo2();883884DisplayMessage(di->T("Load completed"));885886DisplayButtons(DS_BUTTON_CANCEL);887DisplayBanner(DB_LOAD);888889// Allow OK to be pressed as well to confirm the save.890// The PSP only allows cancel, but that's generally not great UX.891// Allowing this here makes it quicker for most users to get into the actual game.892if (IsButtonPressed(cancelButtonFlag) || IsButtonPressed(okButtonFlag)) {893param.GetPspParam()->common.result = SCE_UTILITY_DIALOG_RESULT_SUCCESS;894// Set the save to use for autosave and autoload895param.SetSelectedSave(param.GetFileInfo(currentSelectedSave).idx);896StartFade(false);897}898899EndDraw();900break;901case DS_LOAD_NODATA:902StartDraw();903904DisplayMessage(di->T("There is no data"));905906DisplayButtons(DS_BUTTON_CANCEL);907DisplayBanner(DB_LOAD);908909if (IsButtonPressed(cancelButtonFlag)) {910param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA;911StartFade(false);912}913914EndDraw();915break;916917case DS_DELETE_LIST_CHOICE:918StartDraw();919920DisplaySaveList();921DisplaySaveDataInfo1();922923DisplayButtons(DS_BUTTON_OK | DS_BUTTON_CANCEL);924DisplayBanner(DB_DELETE);925926if (IsButtonPressed(cancelButtonFlag)) {927param.GetPspParam()->common.result = SCE_UTILITY_DIALOG_RESULT_CANCEL;928StartFade(false);929} else if (IsButtonPressed(okButtonFlag)) {930yesnoChoice = 0;931display = DS_DELETE_CONFIRM;932}933934EndDraw();935break;936case DS_DELETE_CONFIRM:937StartDraw();938939DisplaySaveIcon(true);940DisplaySaveDataInfo2();941942DisplayMessage(di->T("DeleteConfirm",943"This save data will be deleted.\nAre you sure you want to continue?"),944true);945946DisplayButtons(DS_BUTTON_OK | DS_BUTTON_CANCEL);947DisplayBanner(DB_DELETE);948949if (IsButtonPressed(cancelButtonFlag) || (IsButtonPressed(okButtonFlag) && yesnoChoice == 0)) {950if(param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTDELETE || param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTALLDELETE)951display = DS_DELETE_LIST_CHOICE;952else {953param.GetPspParam()->common.result = SCE_UTILITY_DIALOG_RESULT_CANCEL;954StartFade(false);955}956} else if (IsButtonPressed(okButtonFlag)) {957display = DS_DELETE_DELETING;958StartIOThread();959}960961EndDraw();962break;963case DS_DELETE_DELETING:964if (ioThreadStatus != SAVEIO_PENDING) {965JoinIOThread();966}967968StartDraw();969970DisplayMessage(di->T("Deleting","Deleting\nPlease Wait..."));971972DisplayBanner(DB_DELETE);973974EndDraw();975break;976case DS_DELETE_FAILED:977JoinIOThread();978StartDraw();979980DisplayMessage(di->T("DeleteFailed", "Unable to delete data."));981982DisplayButtons(DS_BUTTON_CANCEL);983DisplayBanner(DB_DELETE);984985if (IsButtonPressed(cancelButtonFlag)) {986if (param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTDELETE || param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTALLDELETE)987display = DS_DELETE_LIST_CHOICE;988else989StartFade(false);990}991992EndDraw();993break;994case DS_DELETE_DONE:995if (ioThread) {996JoinIOThread();997param.SetPspParam(param.GetPspParam());998}999StartDraw();10001001DisplayMessage(di->T("Delete completed"));10021003DisplayButtons(DS_BUTTON_CANCEL);1004DisplayBanner(DB_DELETE);10051006if (IsButtonPressed(cancelButtonFlag)) {1007if (param.GetFilenameCount() == 0)1008display = DS_DELETE_NODATA;1009else if (param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTDELETE || param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTALLDELETE) {1010if (currentSelectedSave > param.GetFilenameCount() - 1)1011currentSelectedSave = param.GetFilenameCount() - 1;1012display = DS_DELETE_LIST_CHOICE;1013} else {1014param.GetPspParam()->common.result = SCE_UTILITY_DIALOG_RESULT_SUCCESS;1015StartFade(false);1016}1017}10181019EndDraw();1020break;1021case DS_DELETE_NODATA:1022StartDraw();10231024DisplayMessage(di->T("There is no data"));10251026DisplayButtons(DS_BUTTON_CANCEL);1027DisplayBanner(DB_DELETE);10281029if (IsButtonPressed(cancelButtonFlag)) {1030param.GetPspParam()->common.result = SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_DATA;1031StartFade(false);1032}10331034EndDraw();1035break;10361037case DS_NONE: // For action which display nothing1038switch (ioThreadStatus) {1039case SAVEIO_NONE:1040StartIOThread();1041break;1042case SAVEIO_PENDING:1043case SAVEIO_DONE:1044// To make sure there aren't any timing variations, we sync the next frame.1045if (g_Config.iIOTimingMethod == IOTIMING_HOST && ioThreadStatus == SAVEIO_PENDING) {1046// ... except in Host IO timing, where we wait as long as needed.1047break;1048}1049JoinIOThread();1050ChangeStatus(SCE_UTILITY_STATUS_FINISHED, 0);1051break;1052}1053break;10541055default:1056ChangeStatus(SCE_UTILITY_STATUS_FINISHED, 0);1057break;1058}10591060if (ReadStatus() == SCE_UTILITY_STATUS_FINISHED || pendingStatus == SCE_UTILITY_STATUS_FINISHED)1061Memory::Memcpy(requestAddr, &request, request.common.size, "SaveDialogParam");1062param.ClearSFOCache();10631064return 0;1065}10661067void PSPSaveDialog::ExecuteIOAction() {1068param.ClearSFOCache();1069auto &result = param.GetPspParam()->common.result;1070std::lock_guard<std::mutex> guard(paramLock);1071switch (display) {1072case DS_LOAD_LOADING:1073result = param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave);1074if (result == 0) {1075display = DS_LOAD_DONE;1076} else {1077display = DS_LOAD_FAILED;1078}1079break;1080case DS_SAVE_SAVING:1081SaveState::NotifySaveData();1082if (param.Save(param.GetPspParam(), GetSelectedSaveDirName()) == 0) {1083display = DS_SAVE_DONE;1084} else {1085display = DS_SAVE_FAILED;1086}1087break;1088case DS_DELETE_DELETING:1089if (param.Delete(param.GetPspParam(), currentSelectedSave)) {1090result = 0;1091display = DS_DELETE_DONE;1092} else {1093//result = SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_DATA;// What the result should be?1094display = DS_DELETE_FAILED;1095}1096break;1097case DS_NONE:1098ExecuteNotVisibleIOAction();1099break;11001101default:1102// Nothing to do here.1103break;1104}11051106ioThreadStatus = SAVEIO_DONE;1107param.ClearSFOCache();1108}11091110void PSPSaveDialog::ExecuteNotVisibleIOAction() {1111param.ClearSFOCache();1112auto &result = param.GetPspParam()->common.result;11131114SceUtilitySavedataType utilityMode = (SceUtilitySavedataType)(u32)param.GetPspParam()->mode;1115switch (utilityMode) {1116case SCE_UTILITY_SAVEDATA_TYPE_LOAD: // Only load and exit1117case SCE_UTILITY_SAVEDATA_TYPE_AUTOLOAD:1118result = param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave);1119break;1120case SCE_UTILITY_SAVEDATA_TYPE_SAVE: // Only save and exit1121case SCE_UTILITY_SAVEDATA_TYPE_AUTOSAVE:1122SaveState::NotifySaveData();1123result = param.Save(param.GetPspParam(), GetSelectedSaveDirName());1124break;1125case SCE_UTILITY_SAVEDATA_TYPE_SIZES:1126result = param.GetSizes(param.GetPspParam());1127break;1128case SCE_UTILITY_SAVEDATA_TYPE_LIST:1129param.GetList(param.GetPspParam());1130result = 0;1131break;1132case SCE_UTILITY_SAVEDATA_TYPE_FILES:1133result = param.GetFilesList(param.GetPspParam(), requestAddr);1134break;1135case SCE_UTILITY_SAVEDATA_TYPE_GETSIZE:1136{1137bool sizeResult = param.GetSize(param.GetPspParam());1138// TODO: According to JPCSP, should test/verify this part but seems edge casey.1139if (MemoryStick_State() != PSP_MEMORYSTICK_STATE_INSERTED) {1140result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_MEMSTICK;1141} else if (sizeResult) {1142result = 0;1143} else {1144result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;1145}1146}1147break;1148case SCE_UTILITY_SAVEDATA_TYPE_DELETEDATA:1149DEBUG_LOG(Log::sceUtility, "sceUtilitySavedata DELETEDATA: %s", param.GetPspParam()->saveName);1150if (param.Delete(param.GetPspParam(), param.GetSelectedSave())) {1151result = 0;1152} else {1153result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;1154}1155break;1156case SCE_UTILITY_SAVEDATA_TYPE_AUTODELETE:1157case SCE_UTILITY_SAVEDATA_TYPE_DELETE:1158if (param.Delete(param.GetPspParam(), param.GetSelectedSave())) {1159result = 0;1160} else {1161result = SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_DATA;1162}1163break;1164// TODO: Should reset the directory's other files.1165case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATA:1166case SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE:1167SaveState::NotifySaveData();1168result = param.Save(param.GetPspParam(), GetSelectedSaveDirName(), param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_MAKEDATASECURE);1169if (result == SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE) {1170result = SCE_UTILITY_SAVEDATA_ERROR_RW_MEMSTICK_FULL;1171}1172break;1173case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATA:1174case SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE:1175SaveState::NotifySaveData();1176result = param.Save(param.GetPspParam(), GetSelectedSaveDirName(), param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_WRITEDATASECURE);1177break;1178case SCE_UTILITY_SAVEDATA_TYPE_READDATA:1179case SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE:1180result = param.Load(param.GetPspParam(), GetSelectedSaveDirName(), currentSelectedSave, param.GetPspParam()->mode == SCE_UTILITY_SAVEDATA_TYPE_READDATASECURE);1181if (result == SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN)1182result = SCE_UTILITY_SAVEDATA_ERROR_RW_DATA_BROKEN;1183if (result == SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA)1184result = SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;1185break;1186case SCE_UTILITY_SAVEDATA_TYPE_ERASE:1187case SCE_UTILITY_SAVEDATA_TYPE_ERASESECURE:1188result = param.DeleteData(param.GetPspParam());1189break;1190default:1191break;1192}11931194param.ClearSFOCache();1195}11961197void PSPSaveDialog::JoinIOThread() {1198if (ioThread) {1199ioThread->join();1200delete ioThread;1201ioThread = 0;1202}1203}12041205static void DoExecuteIOAction(PSPSaveDialog *dialog) {1206SetCurrentThreadName("SaveIO");12071208AndroidJNIThreadContext jniContext;1209dialog->ExecuteIOAction();1210}12111212void PSPSaveDialog::StartIOThread() {1213if (ioThread) {1214WARN_LOG_REPORT(Log::sceUtility, "Starting a save io thread when one already pending, uh oh.");1215JoinIOThread();1216}12171218ioThreadStatus = SAVEIO_PENDING;1219ioThread = new std::thread(&DoExecuteIOAction, this);1220}12211222int PSPSaveDialog::Shutdown(bool force) {1223if (GetStatus() != SCE_UTILITY_STATUS_FINISHED && !force)1224return SCE_ERROR_UTILITY_INVALID_STATUS;12251226JoinIOThread();1227ioThreadStatus = SAVEIO_NONE;12281229PSPDialog::Shutdown(force);1230if (!force) {1231ChangeStatusShutdown(SAVEDATA_SHUTDOWN_DELAY_US);1232}1233param.SetPspParam(0);1234param.ClearSFOCache();12351236return 0;1237}12381239void PSPSaveDialog::DoState(PointerWrap &p) {1240JoinIOThread();1241PSPDialog::DoState(p);12421243auto s = p.Section("PSPSaveDialog", 1, 2);1244if (!s) {1245return;1246}12471248Do(p, display);1249param.DoState(p);1250Do(p, request);1251// Just reset it.1252bool hasParam = param.GetPspParam() != NULL;1253Do(p, hasParam);1254if (hasParam && p.mode == p.MODE_READ) {1255param.SetPspParam(&request);1256}1257Do(p, requestAddr);1258Do(p, currentSelectedSave);1259Do(p, yesnoChoice);1260if (s > 2) {1261Do(p, ioThreadStatus);1262} else {1263ioThreadStatus = SAVEIO_NONE;1264}1265}12661267pspUtilityDialogCommon *PSPSaveDialog::GetCommonParam() {1268return ¶m.GetPspParam()->common;1269}127012711272