diff --git a/Core/Dialog/PSPGamedataInstallDialog.cpp b/Core/Dialog/PSPGamedataInstallDialog.cpp index af297ba9be..a57de30af7 100644 --- a/Core/Dialog/PSPGamedataInstallDialog.cpp +++ b/Core/Dialog/PSPGamedataInstallDialog.cpp @@ -15,14 +15,28 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include +#include "Common/Common.h" #include "Common/ChunkFile.h" +#include "Core/ELF/ParamSFO.h" #include "Core/MemMapHelpers.h" +#include "Core/Reporting.h" #include "Core/System.h" #include "Core/FileSystems/MetaFileSystem.h" #include "Core/Dialog/PSPGamedataInstallDialog.h" std::string saveBasePath = "ms0:/PSP/SAVEDATA/"; +// Guesses. +const static int GAMEDATA_INIT_DELAY_US = 200000; +const static int GAMEDATA_SHUTDOWN_DELAY_US = 2000; +const static u32 GAMEDATA_BYTES_PER_READ = 32768; +// TODO: Could adjust based on real-time into frame? Or eat cycles? +// If this is too high, some games (e.g. Senjou no Valkyria 3) will lag. +const static u32 GAMEDATA_READS_PER_UPDATE = 20; + +static const std::string SFO_FILENAME = "PARAM.SFO"; + namespace { std::vector GetPSPFileList (std::string dirpath) { @@ -44,92 +58,165 @@ PSPGamedataInstallDialog::~PSPGamedataInstallDialog() { } int PSPGamedataInstallDialog::Init(u32 paramAddr) { -/////////////////////////////////////////////////////// - this->paramAddr = paramAddr; - inFileNames = GetPSPFileList ("disc0:/PSP_GAME/INSDIR"); + if (GetStatus() != SCE_UTILITY_STATUS_NONE) { + ERROR_LOG_REPORT(SCEUTILITY, "A game install request is already running, not starting a new one"); + return SCE_ERROR_UTILITY_INVALID_STATUS; + } + + param.ptr = paramAddr; + inFileNames = GetPSPFileList("disc0:/PSP_GAME/INSDIR"); numFiles = (int)inFileNames.size(); readFiles = 0; progressValue = 0; allFilesSize = 0; allReadSize = 0; - for (auto it = inFileNames.begin(); it != inFileNames.end(); ++it) { - allFilesSize += pspFileSystem.GetFileInfo("disc0:/PSP_GAME/INSDIR/" + (*it)).size; + currentInputFile = 0; + currentOutputFile = 0; + + for (std::string filename : inFileNames) { + allFilesSize += pspFileSystem.GetFileInfo("disc0:/PSP_GAME/INSDIR/" + filename).size; + } + + if (allFilesSize == 0) { + ERROR_LOG_REPORT(SCEUTILITY, "Game install with no files / data"); + // TODO: What happens here? + return -1; } -////////////////////////////////////////////////////// - // Already running - if (status != SCE_UTILITY_STATUS_NONE && status != SCE_UTILITY_STATUS_SHUTDOWN) - return SCE_ERROR_UTILITY_INVALID_STATUS; int size = Memory::Read_U32(paramAddr); memset(&request, 0, sizeof(request)); // Only copy the right size to support different request format Memory::Memcpy(&request, paramAddr, size); - status = SCE_UTILITY_STATUS_INITIALIZE; + ChangeStatusInit(GAMEDATA_INIT_DELAY_US); return 0; } int PSPGamedataInstallDialog::Update(int animSpeed) { - if (status == SCE_UTILITY_STATUS_INITIALIZE){ - status = SCE_UTILITY_STATUS_RUNNING; - } else if (status == SCE_UTILITY_STATUS_RUNNING) { - std::string fullinFileName; - std::string outFileName; - u64 totalLength; - u64 restLength; - u32 bytesToRead = 4096; - u32 inhandle; - u32 outhandle; - size_t readSize; + if (GetStatus() != SCE_UTILITY_STATUS_RUNNING) + return SCE_ERROR_UTILITY_INVALID_STATUS; - if (readFiles < numFiles) { - u8 *temp = new u8[4096]; - fullinFileName = "disc0:/PSP_GAME/INSDIR/" + inFileNames[readFiles]; - outFileName = GetGameDataInstallFileName(&request, inFileNames[readFiles]); - totalLength = pspFileSystem.GetFileInfo(fullinFileName).size; - restLength = totalLength; - inhandle = pspFileSystem.OpenFile(fullinFileName, FILEACCESS_READ); - if (inhandle != 0) { - outhandle = pspFileSystem.OpenFile(outFileName, (FileAccess)(FILEACCESS_WRITE | FILEACCESS_CREATE | FILEACCESS_TRUNCATE)); - if (outhandle != 0) { - while (restLength > 0) { - if (restLength < bytesToRead) - bytesToRead = (u32)restLength; - readSize = pspFileSystem.ReadFile(inhandle, temp, bytesToRead); - if(readSize > 0) { - pspFileSystem.WriteFile(outhandle, temp, readSize); - restLength -= readSize; - allReadSize += readSize; - } else - break; - } - pspFileSystem.CloseFile(outhandle); - } - ++readFiles; - pspFileSystem.CloseFile(inhandle); - } - updateProgress(); - delete[] temp; + if (readFiles < numFiles) { + if (currentInputFile != 0 && currentOutputFile != 0) { + // Continue copying, this will close once done automatically. + CopyCurrentFileData(); } else { - //What is this? - request.unknownResult1 = readFiles; - request.unknownResult2 = readFiles; - Memory::WriteStruct(paramAddr,&request); - - status = SCE_UTILITY_STATUS_FINISHED; + OpenNextFile(); } - } else if (status == SCE_UTILITY_STATUS_FINISHED) { - status = SCE_UTILITY_STATUS_SHUTDOWN; - } + + UpdateProgress(); + } else { + WriteSfoFile(); + + // TODO: What is this? Should one of these update per file or anything? + request.unknownResult1 = readFiles; + request.unknownResult2 = readFiles; + Memory::WriteStruct(param.ptr, &request); + + ChangeStatus(SCE_UTILITY_STATUS_FINISHED, 0); + } return 0; } +void PSPGamedataInstallDialog::OpenNextFile() { + std::string inputFileName = "disc0:/PSP_GAME/INSDIR/" + inFileNames[readFiles]; + std::string outputFileName = GetGameDataInstallFileName(&request, inFileNames[readFiles]); + + currentInputFile = pspFileSystem.OpenFile(inputFileName, FILEACCESS_READ); + if (!currentInputFile) { + // TODO: Generate an error code? + ERROR_LOG_REPORT(SCEUTILITY, "Unable to read from install file: %s", inFileNames[readFiles].c_str()); + ++readFiles; + return; + } + currentOutputFile = pspFileSystem.OpenFile(outputFileName, (FileAccess)(FILEACCESS_WRITE | FILEACCESS_CREATE | FILEACCESS_TRUNCATE)); + if (!currentOutputFile) { + // TODO: Generate an error code? + ERROR_LOG(SCEUTILITY, "Unable to write to install file: %s", inFileNames[readFiles].c_str()); + pspFileSystem.CloseFile(currentInputFile); + currentInputFile = 0; + ++readFiles; + return; + } + + currentInputBytesLeft = (u32)pspFileSystem.GetFileInfo(inputFileName).size; +} + +void PSPGamedataInstallDialog::CopyCurrentFileData() { + u8 buffer[GAMEDATA_BYTES_PER_READ]; + for (u32 i = 0; i < GAMEDATA_READS_PER_UPDATE; ++i) { + if (currentInputBytesLeft <= 0) { + break; + } + + const u32 bytesToRead = std::min(GAMEDATA_BYTES_PER_READ, currentInputBytesLeft); + size_t readSize = pspFileSystem.ReadFile(currentInputFile, buffer, bytesToRead); + if (readSize > 0) { + pspFileSystem.WriteFile(currentOutputFile, buffer, readSize); + currentInputBytesLeft -= (u32)readSize; + allReadSize += readSize; + } else { + break; + } + } + + if (currentInputBytesLeft <= 0) { + CloseCurrentFile(); + } +} + +void PSPGamedataInstallDialog::CloseCurrentFile() { + pspFileSystem.CloseFile(currentOutputFile); + currentOutputFile = 0; + + pspFileSystem.CloseFile(currentInputFile); + currentInputFile = 0; + + ++readFiles; +} + +void PSPGamedataInstallDialog::WriteSfoFile() { + ParamSFOData sfoFile; + std::string sfopath = GetGameDataInstallFileName(&request, SFO_FILENAME); + PSPFileInfo sfoInfo = pspFileSystem.GetFileInfo(sfopath); + if (sfoInfo.exists) { + std::vector sfoData; + if (pspFileSystem.ReadEntireFile(sfopath, sfoData) >= 0) { + sfoFile.ReadSFO(sfoData); + } + } + + // Update based on the just-saved data. + sfoFile.SetValue("TITLE", param->sfoParam.title, 128); + sfoFile.SetValue("SAVEDATA_TITLE", param->sfoParam.savedataTitle, 128); + sfoFile.SetValue("SAVEDATA_DETAIL", param->sfoParam.detail, 1024); + sfoFile.SetValue("PARENTAL_LEVEL", param->sfoParam.parentalLevel, 4); + // TODO: Verify category. + sfoFile.SetValue("CATEGORY", "MS", 4); + sfoFile.SetValue("SAVEDATA_DIRECTORY", std::string(param->gameName) + param->dataName, 64); + + // TODO: Maybe there should be other things in the SFO file? Needs testing. + + u8 *sfoData; + size_t sfoSize; + sfoFile.WriteSFO(&sfoData,&sfoSize); + + u32 handle = pspFileSystem.OpenFile(sfopath, (FileAccess)(FILEACCESS_WRITE | FILEACCESS_CREATE | FILEACCESS_TRUNCATE)); + if (handle != 0) { + pspFileSystem.WriteFile(handle, sfoData, sfoSize); + pspFileSystem.CloseFile(handle); + } + + delete[] sfoData; +} + int PSPGamedataInstallDialog::Abort() { + // TODO: Delete the files or anything? return PSPDialog::Shutdown(); } int PSPGamedataInstallDialog::Shutdown(bool force) { - if (status != SCE_UTILITY_STATUS_FINISHED && !force) + if (GetStatus() != SCE_UTILITY_STATUS_FINISHED && !force) return SCE_ERROR_UTILITY_INVALID_STATUS; return PSPDialog::Shutdown(force); @@ -145,20 +232,19 @@ std::string PSPGamedataInstallDialog::GetGameDataInstallFileName(SceUtilityGamed return GameDataInstallPath + filename; } -void PSPGamedataInstallDialog::updateProgress() { +void PSPGamedataInstallDialog::UpdateProgress() { // Update progress bar(if there is). - // progress value is progress[3] << 24 | progress[2] << 16 | progress[1] << 8 | progress[0]. // We only should update progress[0] here as the max progress value is 100. if (allFilesSize != 0) - progressValue = (int)(allReadSize / allFilesSize) * 100; + progressValue = (int)((allReadSize * 100) / allFilesSize); else progressValue = 100; - request.progress[0] = progressValue; - Memory::WriteStruct(paramAddr,&request); + request.progress = progressValue; + Memory::WriteStruct(param.ptr, &request); } void PSPGamedataInstallDialog::DoState(PointerWrap &p) { - auto s = p.Section("PSPGamedataInstallDialog", 0, 2); + auto s = p.Section("PSPGamedataInstallDialog", 0, 3); if (!s) return; @@ -168,7 +254,7 @@ void PSPGamedataInstallDialog::DoState(PointerWrap &p) { // This was included in version 2 and higher. if (s > 2) { - p.Do(paramAddr); + p.Do(param.ptr); p.Do(inFileNames); p.Do(numFiles); p.Do(readFiles); @@ -176,6 +262,16 @@ void PSPGamedataInstallDialog::DoState(PointerWrap &p) { p.Do(allReadSize); p.Do(progressValue); } else { - paramAddr = 0; + param.ptr = 0; + } + + if (s > 3) { + p.Do(currentInputFile); + p.Do(currentInputBytesLeft); + p.Do(currentOutputFile); + } else { + currentInputFile = 0; + currentInputBytesLeft = 0; + currentOutputFile = 0; } } diff --git a/Core/Dialog/PSPGamedataInstallDialog.h b/Core/Dialog/PSPGamedataInstallDialog.h index 0ec969443b..85d0c33fc4 100644 --- a/Core/Dialog/PSPGamedataInstallDialog.h +++ b/Core/Dialog/PSPGamedataInstallDialog.h @@ -18,6 +18,7 @@ #pragma once #include "Core/Dialog/PSPDialog.h" +#include "Core/Dialog/SavedataParam.h" struct SceUtilityGamedataInstallParam { pspUtilityDialogCommon common; @@ -25,12 +26,8 @@ struct SceUtilityGamedataInstallParam { char gameName[13]; char ignore1[3]; char dataName[20]; - char gamedataParamsGameTitle[128]; - char gamedataParamsDataTitle[128]; - char gamedataParamsData[1024]; - u8 unknown2; - char ignore2[3]; - char progress[4]; // This is progress value,should be updated. + PspUtilitySavedataSFOParam sfoParam; + int progress; u32_le unknownResult1; u32_le unknownResult2; char ignore3[48]; @@ -50,8 +47,14 @@ public: std::string GetGameDataInstallFileName(SceUtilityGamedataInstallParam *param, std::string filename); private: + void UpdateProgress(); + void OpenNextFile(); + void CopyCurrentFileData(); + void CloseCurrentFile(); + void WriteSfoFile(); + SceUtilityGamedataInstallParam request; - u32 paramAddr; + PSPPointer param; std::vector inFileNames; int numFiles; int readFiles; @@ -59,5 +62,7 @@ private: u64 allReadSize; // use this to calculate progress value. int progressValue; - void updateProgress(); + u32 currentInputFile; + u32 currentInputBytesLeft; + u32 currentOutputFile; };