2013-11-20 13:42:48 +00:00
|
|
|
|
// Copyright (c) 2013- PPSSPP Project.
|
|
|
|
|
|
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
|
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
|
|
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
|
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
|
|
|
|
|
|
// Official git repository and contact information can be found at
|
|
|
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
|
|
2013-12-29 22:44:35 +00:00
|
|
|
|
#include <algorithm>
|
2019-07-14 09:32:32 +00:00
|
|
|
|
#include <cctype>
|
2020-08-10 14:18:49 +00:00
|
|
|
|
#include <cstring>
|
2019-07-14 09:32:32 +00:00
|
|
|
|
#include <string>
|
2013-12-29 22:44:35 +00:00
|
|
|
|
#include <set>
|
2020-08-10 12:31:31 +00:00
|
|
|
|
#include <sstream>
|
2017-02-27 19:51:36 +00:00
|
|
|
|
#include <thread>
|
2013-12-29 22:44:35 +00:00
|
|
|
|
|
2015-09-16 03:59:31 +00:00
|
|
|
|
#ifdef SHARED_LIBZIP
|
|
|
|
|
#include <zip.h>
|
|
|
|
|
#else
|
2013-12-05 16:49:37 +00:00
|
|
|
|
#include "ext/libzip/zip.h"
|
2015-09-16 03:59:31 +00:00
|
|
|
|
#endif
|
2021-01-09 22:45:03 +00:00
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
#include "Common/CommonWindows.h"
|
|
|
|
|
#endif
|
2020-10-01 11:05:04 +00:00
|
|
|
|
#include "Common/Data/Encoding/Utf8.h"
|
2020-10-03 22:25:21 +00:00
|
|
|
|
#include "Common/Data/Format/IniFile.h"
|
2013-11-20 13:42:48 +00:00
|
|
|
|
#include "Common/Log.h"
|
2020-10-04 18:48:47 +00:00
|
|
|
|
#include "Common/File/FileUtil.h"
|
2019-07-10 21:37:10 +00:00
|
|
|
|
#include "Common/StringUtils.h"
|
2013-11-20 15:36:58 +00:00
|
|
|
|
#include "Core/Config.h"
|
2019-07-15 00:43:51 +00:00
|
|
|
|
#include "Core/Loaders.h"
|
|
|
|
|
#include "Core/ELF/ParamSFO.h"
|
|
|
|
|
#include "Core/ELF/PBPReader.h"
|
2013-11-20 13:42:48 +00:00
|
|
|
|
#include "Core/System.h"
|
2019-07-15 00:43:51 +00:00
|
|
|
|
#include "Core/FileSystems/ISOFileSystem.h"
|
2013-11-20 13:42:48 +00:00
|
|
|
|
#include "Core/Util/GameManager.h"
|
2020-10-01 11:05:04 +00:00
|
|
|
|
#include "Common/Data/Text/I18n.h"
|
2013-11-20 13:42:48 +00:00
|
|
|
|
|
|
|
|
|
GameManager g_GameManager;
|
|
|
|
|
|
2019-09-28 18:30:43 +00:00
|
|
|
|
GameManager::GameManager() {
|
2013-12-05 12:01:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-11-20 15:36:58 +00:00
|
|
|
|
std::string GameManager::GetTempFilename() const {
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
wchar_t tempPath[MAX_PATH];
|
|
|
|
|
GetTempPath(MAX_PATH, tempPath);
|
|
|
|
|
wchar_t buffer[MAX_PATH];
|
|
|
|
|
GetTempFileName(tempPath, L"PSP", 1, buffer);
|
|
|
|
|
return ConvertWStringToUTF8(buffer);
|
|
|
|
|
#else
|
2020-03-04 06:53:03 +00:00
|
|
|
|
return g_Config.memStickDirectory + "ppsspp.dl";
|
2013-11-20 15:36:58 +00:00
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-20 13:42:48 +00:00
|
|
|
|
bool GameManager::IsGameInstalled(std::string name) {
|
2013-11-20 15:36:58 +00:00
|
|
|
|
std::string pspGame = GetSysDirectory(DIRECTORY_GAME);
|
|
|
|
|
return File::Exists(pspGame + name);
|
2013-11-20 13:42:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-08 21:03:27 +00:00
|
|
|
|
bool GameManager::DownloadAndInstall(std::string storeFileUrl) {
|
2019-09-28 18:30:43 +00:00
|
|
|
|
if (curDownload_.get() != nullptr) {
|
2013-11-20 13:42:48 +00:00
|
|
|
|
ERROR_LOG(HLE, "Can only process one download at a time");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2013-12-05 12:01:00 +00:00
|
|
|
|
if (installInProgress_) {
|
|
|
|
|
ERROR_LOG(HLE, "Can't download when an install is in progress (yet)");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2013-11-20 13:42:48 +00:00
|
|
|
|
|
2013-11-20 15:36:58 +00:00
|
|
|
|
std::string filename = GetTempFilename();
|
2019-07-08 21:03:27 +00:00
|
|
|
|
curDownload_ = g_DownloadManager.StartDownload(storeFileUrl, filename);
|
2013-11-20 13:42:48 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-01 17:49:44 +00:00
|
|
|
|
bool GameManager::IsDownloading(std::string storeZipUrl) {
|
|
|
|
|
if (curDownload_)
|
|
|
|
|
return curDownload_->url() == storeZipUrl;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-14 09:32:32 +00:00
|
|
|
|
bool GameManager::CancelDownload() {
|
2017-03-06 14:43:38 +00:00
|
|
|
|
if (!curDownload_)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
curDownload_->Cancel();
|
|
|
|
|
curDownload_.reset();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-01 17:40:34 +00:00
|
|
|
|
float GameManager::DownloadSpeedKBps() {
|
|
|
|
|
if (curDownload_)
|
|
|
|
|
return curDownload_->SpeedKBps();
|
|
|
|
|
return 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-20 18:48:44 +00:00
|
|
|
|
bool GameManager::Uninstall(std::string name) {
|
|
|
|
|
if (name.empty()) {
|
|
|
|
|
ERROR_LOG(HLE, "Cannot remove an empty-named game");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2013-11-20 15:36:58 +00:00
|
|
|
|
std::string gameDir = GetSysDirectory(DIRECTORY_GAME) + name;
|
2019-07-14 09:32:32 +00:00
|
|
|
|
INFO_LOG(HLE, "Deleting '%s'", gameDir.c_str());
|
2013-11-20 15:36:58 +00:00
|
|
|
|
if (!File::Exists(gameDir)) {
|
2019-07-14 09:32:32 +00:00
|
|
|
|
ERROR_LOG(HLE, "Game '%s' not installed, cannot uninstall", name.c_str());
|
2013-11-20 18:48:44 +00:00
|
|
|
|
return false;
|
2013-11-20 15:36:58 +00:00
|
|
|
|
}
|
2013-11-20 13:42:48 +00:00
|
|
|
|
|
2013-11-20 15:36:58 +00:00
|
|
|
|
bool success = File::DeleteDirRecursively(gameDir);
|
|
|
|
|
if (success) {
|
2019-07-14 09:32:32 +00:00
|
|
|
|
INFO_LOG(HLE, "Successfully deleted game '%s'", name.c_str());
|
2013-11-20 15:36:58 +00:00
|
|
|
|
g_Config.CleanRecent();
|
2013-11-20 18:48:44 +00:00
|
|
|
|
return true;
|
2013-11-20 15:36:58 +00:00
|
|
|
|
} else {
|
2019-07-14 09:32:32 +00:00
|
|
|
|
ERROR_LOG(HLE, "Failed to delete game '%s'", name.c_str());
|
2013-11-20 18:48:44 +00:00
|
|
|
|
return false;
|
2013-11-20 15:36:58 +00:00
|
|
|
|
}
|
2013-11-20 13:42:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameManager::Update() {
|
|
|
|
|
if (curDownload_.get() && curDownload_->Done()) {
|
2019-07-14 09:32:32 +00:00
|
|
|
|
INFO_LOG(HLE, "Download completed! Status = %d", curDownload_->ResultCode());
|
2019-07-08 21:03:27 +00:00
|
|
|
|
std::string fileName = curDownload_->outfile();
|
2013-11-20 15:36:58 +00:00
|
|
|
|
if (curDownload_->ResultCode() == 200) {
|
2019-07-08 21:03:27 +00:00
|
|
|
|
if (!File::Exists(fileName)) {
|
2019-07-14 09:32:32 +00:00
|
|
|
|
ERROR_LOG(HLE, "Downloaded file '%s' does not exist :(", fileName.c_str());
|
2013-11-20 15:36:58 +00:00
|
|
|
|
curDownload_.reset();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-05-18 12:21:13 +00:00
|
|
|
|
// Game downloaded to temporary file - install it!
|
2019-07-10 21:37:10 +00:00
|
|
|
|
InstallGameOnThread(curDownload_->url(), fileName, true);
|
2013-11-29 16:34:21 +00:00
|
|
|
|
} else {
|
2019-07-10 21:37:10 +00:00
|
|
|
|
ERROR_LOG(HLE, "Expected HTTP status code 200, got status code %d. Install cancelled, deleting partial file '%s'",
|
|
|
|
|
curDownload_->ResultCode(), fileName.c_str());
|
2019-07-08 21:03:27 +00:00
|
|
|
|
File::Delete(fileName.c_str());
|
2013-11-20 15:36:58 +00:00
|
|
|
|
}
|
2013-11-29 15:33:17 +00:00
|
|
|
|
curDownload_.reset();
|
2013-11-20 13:42:48 +00:00
|
|
|
|
}
|
2019-09-28 18:30:43 +00:00
|
|
|
|
|
|
|
|
|
if (installDonePending_) {
|
|
|
|
|
if (installThread_.get() != nullptr) {
|
|
|
|
|
if (installThread_->joinable())
|
|
|
|
|
installThread_->join();
|
|
|
|
|
installThread_.reset();
|
|
|
|
|
}
|
|
|
|
|
installDonePending_ = false;
|
|
|
|
|
}
|
2013-11-20 13:42:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-27 13:17:56 +00:00
|
|
|
|
static void countSlashes(const std::string &fileName, int *slashLocation, int *slashCount) {
|
2019-07-07 21:12:26 +00:00
|
|
|
|
*slashCount = 0;
|
|
|
|
|
int lastSlashLocation = -1;
|
|
|
|
|
*slashLocation = -1;
|
|
|
|
|
for (size_t i = 0; i < fileName.size(); i++) {
|
|
|
|
|
if (fileName[i] == '/') {
|
|
|
|
|
(*slashCount)++;
|
|
|
|
|
*slashLocation = lastSlashLocation;
|
|
|
|
|
lastSlashLocation = (int)i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-08 21:03:27 +00:00
|
|
|
|
ZipFileContents DetectZipFileContents(std::string fileName, ZipFileInfo *info) {
|
|
|
|
|
int error = 0;
|
2013-11-20 15:36:58 +00:00
|
|
|
|
#ifdef _WIN32
|
2019-07-08 21:03:27 +00:00
|
|
|
|
struct zip *z = zip_open(ConvertUTF8ToWString(fileName).c_str(), 0, &error);
|
2013-11-20 15:36:58 +00:00
|
|
|
|
#else
|
2019-07-08 21:26:22 +00:00
|
|
|
|
struct zip *z = zip_open(fileName.c_str(), 0, &error);
|
2013-11-20 15:36:58 +00:00
|
|
|
|
#endif
|
2013-11-20 13:42:48 +00:00
|
|
|
|
if (!z) {
|
2019-07-08 21:03:27 +00:00
|
|
|
|
return ZipFileContents::UNKNOWN;
|
2013-11-20 13:42:48 +00:00
|
|
|
|
}
|
2019-07-08 21:03:27 +00:00
|
|
|
|
ZipFileContents retVal = DetectZipFileContents(z, info);
|
|
|
|
|
zip_close(z);
|
|
|
|
|
return retVal;
|
|
|
|
|
}
|
2013-11-20 13:42:48 +00:00
|
|
|
|
|
2019-07-14 09:32:32 +00:00
|
|
|
|
inline char asciitolower(char in) {
|
|
|
|
|
if (in <= 'Z' && in >= 'A')
|
|
|
|
|
return in - ('Z' - 'z');
|
|
|
|
|
return in;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-08 21:03:27 +00:00
|
|
|
|
ZipFileContents DetectZipFileContents(struct zip *z, ZipFileInfo *info) {
|
2013-11-20 13:42:48 +00:00
|
|
|
|
int numFiles = zip_get_num_files(z);
|
|
|
|
|
|
2019-07-07 21:55:17 +00:00
|
|
|
|
// Verify that this is a PSP zip file with the correct layout. We also try
|
2019-07-07 21:12:26 +00:00
|
|
|
|
// to detect simple zipped ISO files, those we'll just "install" to the current
|
|
|
|
|
// directory of the Games tab (where else?).
|
|
|
|
|
bool isPSPMemstickGame = false;
|
|
|
|
|
bool isZippedISO = false;
|
2019-07-15 00:43:51 +00:00
|
|
|
|
bool isTexturePack = false;
|
2013-11-29 16:34:21 +00:00
|
|
|
|
int stripChars = 0;
|
2019-07-07 21:12:26 +00:00
|
|
|
|
int isoFileIndex = -1;
|
2019-07-15 00:43:51 +00:00
|
|
|
|
int stripCharsTexturePack = -1;
|
|
|
|
|
int textureIniIndex = -1;
|
2013-12-05 13:11:35 +00:00
|
|
|
|
|
2013-11-20 15:36:58 +00:00
|
|
|
|
for (int i = 0; i < numFiles; i++) {
|
|
|
|
|
const char *fn = zip_get_name(z, i, 0);
|
2013-11-20 15:50:09 +00:00
|
|
|
|
std::string zippedName = fn;
|
2019-07-14 09:32:32 +00:00
|
|
|
|
std::transform(zippedName.begin(), zippedName.end(), zippedName.begin(),
|
|
|
|
|
[](unsigned char c) { return asciitolower(c); }); // Not using std::tolower to avoid Turkish I->ı conversion.
|
|
|
|
|
if (zippedName.find("eboot.pbp") != std::string::npos) {
|
2013-11-20 15:50:09 +00:00
|
|
|
|
int slashCount = 0;
|
2013-12-05 12:22:06 +00:00
|
|
|
|
int slashLocation = -1;
|
2019-07-07 21:12:26 +00:00
|
|
|
|
countSlashes(zippedName, &slashLocation, &slashCount);
|
|
|
|
|
if (slashCount >= 1 && (!isPSPMemstickGame || slashLocation < stripChars + 1)) {
|
2013-11-29 16:34:21 +00:00
|
|
|
|
stripChars = slashLocation + 1;
|
2019-07-07 21:12:26 +00:00
|
|
|
|
isPSPMemstickGame = true;
|
2013-11-20 15:50:09 +00:00
|
|
|
|
} else {
|
2019-07-14 09:32:32 +00:00
|
|
|
|
INFO_LOG(HLE, "Wrong number of slashes (%i) in '%s'", slashCount, fn);
|
2013-11-20 15:50:09 +00:00
|
|
|
|
}
|
2019-07-14 09:32:32 +00:00
|
|
|
|
} else if (endsWith(zippedName, ".iso") || endsWith(zippedName, ".cso")) {
|
2019-07-07 21:12:26 +00:00
|
|
|
|
int slashCount = 0;
|
|
|
|
|
int slashLocation = -1;
|
|
|
|
|
countSlashes(zippedName, &slashLocation, &slashCount);
|
2019-07-07 21:55:17 +00:00
|
|
|
|
if (slashCount <= 1) {
|
|
|
|
|
// We only do this if the ISO file is in the root or one level down.
|
2019-07-07 21:12:26 +00:00
|
|
|
|
isZippedISO = true;
|
|
|
|
|
isoFileIndex = i;
|
|
|
|
|
}
|
2019-07-15 00:43:51 +00:00
|
|
|
|
} else if (zippedName.find("textures.ini") != std::string::npos) {
|
2019-07-27 13:17:56 +00:00
|
|
|
|
int slashLocation = (int)zippedName.find_last_of('/');
|
2019-07-15 00:43:51 +00:00
|
|
|
|
if (stripCharsTexturePack == -1 || slashLocation < stripCharsTexturePack + 1) {
|
|
|
|
|
stripCharsTexturePack = slashLocation + 1;
|
|
|
|
|
isTexturePack = true;
|
|
|
|
|
textureIniIndex = i;
|
|
|
|
|
}
|
2013-11-20 15:50:09 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-08 21:03:27 +00:00
|
|
|
|
info->stripChars = stripChars;
|
|
|
|
|
info->numFiles = numFiles;
|
|
|
|
|
info->isoFileIndex = isoFileIndex;
|
2019-07-15 00:43:51 +00:00
|
|
|
|
info->textureIniIndex = textureIniIndex;
|
|
|
|
|
info->ignoreMetaFiles = false;
|
|
|
|
|
|
2019-07-07 21:55:17 +00:00
|
|
|
|
// If a ZIP is detected as both, let's let the memstick game interpretation prevail.
|
2019-07-07 21:12:26 +00:00
|
|
|
|
if (isPSPMemstickGame) {
|
2019-07-08 21:03:27 +00:00
|
|
|
|
return ZipFileContents::PSP_GAME_DIR;
|
2019-07-07 21:12:26 +00:00
|
|
|
|
} else if (isZippedISO) {
|
2019-07-08 21:03:27 +00:00
|
|
|
|
return ZipFileContents::ISO_FILE;
|
2019-07-15 00:43:51 +00:00
|
|
|
|
} else if (isTexturePack) {
|
|
|
|
|
info->stripChars = stripCharsTexturePack;
|
|
|
|
|
info->ignoreMetaFiles = true;
|
|
|
|
|
return ZipFileContents::TEXTURE_PACK;
|
2019-07-07 21:12:26 +00:00
|
|
|
|
} else {
|
2019-07-08 21:03:27 +00:00
|
|
|
|
return ZipFileContents::UNKNOWN;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-14 09:32:32 +00:00
|
|
|
|
bool GameManager::InstallGame(const std::string &url, const std::string &fileName, bool deleteAfter) {
|
2019-07-08 21:03:27 +00:00
|
|
|
|
if (installInProgress_) {
|
|
|
|
|
ERROR_LOG(HLE, "Cannot have two installs in progress at the same time");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!File::Exists(fileName)) {
|
2019-07-14 09:32:32 +00:00
|
|
|
|
ERROR_LOG(HLE, "Game file '%s' doesn't exist", fileName.c_str());
|
2019-07-08 21:03:27 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-10 21:37:10 +00:00
|
|
|
|
// Examine the URL to guess out what we're installing.
|
|
|
|
|
if (endsWithNoCase(url, ".cso") || endsWithNoCase(url, ".iso")) {
|
|
|
|
|
// It's a raw ISO or CSO file. We just copy it to the destination.
|
|
|
|
|
std::string shortFilename = GetFilenameFromPath(url);
|
|
|
|
|
return InstallRawISO(fileName, shortFilename, deleteAfter);
|
2019-07-08 21:03:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-26 18:43:18 +00:00
|
|
|
|
auto sy = GetI18NCategory("System");
|
2019-07-08 21:03:27 +00:00
|
|
|
|
installInProgress_ = true;
|
|
|
|
|
|
|
|
|
|
std::string pspGame = GetSysDirectory(DIRECTORY_GAME);
|
2019-07-15 00:43:51 +00:00
|
|
|
|
std::string dest = pspGame;
|
2019-07-08 21:03:27 +00:00
|
|
|
|
int error = 0;
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
struct zip *z = zip_open(ConvertUTF8ToWString(fileName).c_str(), 0, &error);
|
|
|
|
|
#else
|
2019-07-08 21:26:22 +00:00
|
|
|
|
struct zip *z = zip_open(fileName.c_str(), 0, &error);
|
2019-07-08 21:03:27 +00:00
|
|
|
|
#endif
|
|
|
|
|
if (!z) {
|
2019-07-14 09:32:32 +00:00
|
|
|
|
ERROR_LOG(HLE, "Failed to open ZIP file '%s', error code=%i", fileName.c_str(), error);
|
2019-07-08 21:03:27 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ZipFileInfo info;
|
|
|
|
|
ZipFileContents contents = DetectZipFileContents(z, &info);
|
|
|
|
|
switch (contents) {
|
|
|
|
|
case ZipFileContents::PSP_GAME_DIR:
|
2019-07-15 00:43:51 +00:00
|
|
|
|
INFO_LOG(HLE, "Installing '%s' into '%s'", fileName.c_str(), pspGame.c_str());
|
2019-07-08 21:03:27 +00:00
|
|
|
|
// InstallMemstickGame contains code to close z.
|
2019-07-15 00:43:51 +00:00
|
|
|
|
return InstallMemstickGame(z, fileName, pspGame, info, false, deleteAfter);
|
2019-07-08 21:03:27 +00:00
|
|
|
|
case ZipFileContents::ISO_FILE:
|
2019-07-15 00:43:51 +00:00
|
|
|
|
INFO_LOG(HLE, "Installing '%s' into its containing directory", fileName.c_str());
|
|
|
|
|
// InstallZippedISO contains code to close z.
|
2019-07-08 21:03:27 +00:00
|
|
|
|
return InstallZippedISO(z, info.isoFileIndex, fileName, deleteAfter);
|
2019-07-15 00:43:51 +00:00
|
|
|
|
case ZipFileContents::TEXTURE_PACK:
|
|
|
|
|
// InstallMemstickGame contains code to close z, and works for textures too.
|
|
|
|
|
if (DetectTexturePackDest(z, info.textureIniIndex, &dest)) {
|
|
|
|
|
INFO_LOG(HLE, "Installing '%s' into '%s'", fileName.c_str(), dest.c_str());
|
|
|
|
|
File::CreateFullPath(dest);
|
|
|
|
|
File::CreateEmptyFile(dest + "/.nomedia");
|
|
|
|
|
return InstallMemstickGame(z, fileName, dest, info, true, deleteAfter);
|
2019-07-16 03:57:43 +00:00
|
|
|
|
} else {
|
|
|
|
|
zip_close(z);
|
|
|
|
|
z = nullptr;
|
2019-07-15 00:43:51 +00:00
|
|
|
|
}
|
|
|
|
|
return false;
|
2019-07-08 21:03:27 +00:00
|
|
|
|
default:
|
2013-11-29 16:34:21 +00:00
|
|
|
|
ERROR_LOG(HLE, "File not a PSP game, no EBOOT.PBP found.");
|
2019-07-15 00:43:51 +00:00
|
|
|
|
SetInstallError(sy->T("Not a PSP game"));
|
2019-07-16 03:57:43 +00:00
|
|
|
|
zip_close(z);
|
|
|
|
|
z = nullptr;
|
2017-05-18 12:21:13 +00:00
|
|
|
|
if (deleteAfter)
|
2019-07-08 21:03:27 +00:00
|
|
|
|
File::Delete(fileName);
|
2013-12-05 12:01:00 +00:00
|
|
|
|
return false;
|
2013-11-20 15:36:58 +00:00
|
|
|
|
}
|
2019-07-07 21:12:26 +00:00
|
|
|
|
}
|
2013-11-20 15:36:58 +00:00
|
|
|
|
|
2019-07-15 00:43:51 +00:00
|
|
|
|
bool GameManager::DetectTexturePackDest(struct zip *z, int iniIndex, std::string *dest) {
|
2020-01-26 18:43:18 +00:00
|
|
|
|
auto iz = GetI18NCategory("InstallZip");
|
2019-07-15 00:43:51 +00:00
|
|
|
|
|
|
|
|
|
struct zip_stat zstat;
|
|
|
|
|
zip_stat_index(z, iniIndex, 0, &zstat);
|
|
|
|
|
|
|
|
|
|
if (zstat.size >= 32 * 1024 * 1024) {
|
|
|
|
|
SetInstallError(iz->T("Texture pack doesn't support install"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string buffer;
|
|
|
|
|
buffer.resize(zstat.size);
|
|
|
|
|
zip_file *zf = zip_fopen_index(z, iniIndex, 0);
|
|
|
|
|
if (zip_fread(zf, &buffer[0], buffer.size()) != (ssize_t)zstat.size) {
|
|
|
|
|
SetInstallError(iz->T("Zip archive corrupt"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IniFile ini;
|
|
|
|
|
std::stringstream sstream(buffer);
|
|
|
|
|
ini.Load(sstream);
|
|
|
|
|
|
|
|
|
|
auto games = ini.GetOrCreateSection("games")->ToMap();
|
|
|
|
|
if (games.empty()) {
|
|
|
|
|
SetInstallError(iz->T("Texture pack doesn't support install"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string gameID = games.begin()->first;
|
|
|
|
|
if (games.size() > 1) {
|
|
|
|
|
// Check for any supported game on their recent list and use that instead.
|
|
|
|
|
for (const std::string &path : g_Config.recentIsos) {
|
|
|
|
|
std::string recentID = GetGameID(path);
|
|
|
|
|
if (games.find(recentID) != games.end()) {
|
|
|
|
|
gameID = recentID;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string pspTextures = GetSysDirectory(DIRECTORY_TEXTURES);
|
|
|
|
|
*dest = pspTextures + gameID + "/";
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameManager::SetInstallError(const std::string &err) {
|
|
|
|
|
installProgress_ = 0.0f;
|
|
|
|
|
installInProgress_ = false;
|
|
|
|
|
installError_ = err;
|
|
|
|
|
InstallDone();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string GameManager::GetGameID(const std::string &path) const {
|
|
|
|
|
auto loader = ConstructFileLoader(path);
|
|
|
|
|
std::string id;
|
|
|
|
|
|
|
|
|
|
switch (Identify_File(loader)) {
|
|
|
|
|
case IdentifiedFileType::PSP_PBP_DIRECTORY:
|
|
|
|
|
delete loader;
|
|
|
|
|
loader = ConstructFileLoader(ResolvePBPFile(path));
|
|
|
|
|
id = GetPBPGameID(loader);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case IdentifiedFileType::PSP_PBP:
|
|
|
|
|
id = GetPBPGameID(loader);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case IdentifiedFileType::PSP_ISO:
|
|
|
|
|
case IdentifiedFileType::PSP_ISO_NP:
|
|
|
|
|
id = GetISOGameID(loader);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
id.clear();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete loader;
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string GameManager::GetPBPGameID(FileLoader *loader) const {
|
|
|
|
|
PBPReader pbp(loader);
|
|
|
|
|
std::vector<u8> sfoData;
|
|
|
|
|
if (pbp.GetSubFile(PBP_PARAM_SFO, &sfoData)) {
|
|
|
|
|
ParamSFOData sfo;
|
|
|
|
|
sfo.ReadSFO(sfoData);
|
|
|
|
|
return sfo.GetValueString("DISC_ID");
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string GameManager::GetISOGameID(FileLoader *loader) const {
|
|
|
|
|
SequentialHandleAllocator handles;
|
|
|
|
|
BlockDevice *bd = constructBlockDevice(loader);
|
|
|
|
|
if (!bd) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
ISOFileSystem umd(&handles, bd);
|
|
|
|
|
|
|
|
|
|
PSPFileInfo info = umd.GetFileInfo("/PSP_GAME/PARAM.SFO");
|
2019-10-20 18:03:37 +00:00
|
|
|
|
int handle = -1;
|
2019-07-15 00:43:51 +00:00
|
|
|
|
if (info.exists) {
|
|
|
|
|
handle = umd.OpenFile("/PSP_GAME/PARAM.SFO", FILEACCESS_READ);
|
|
|
|
|
}
|
2019-10-20 18:03:37 +00:00
|
|
|
|
if (handle < 0) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
2019-07-15 00:43:51 +00:00
|
|
|
|
|
|
|
|
|
std::string sfoData;
|
|
|
|
|
sfoData.resize(info.size);
|
|
|
|
|
umd.ReadFile(handle, (u8 *)&sfoData[0], info.size);
|
|
|
|
|
umd.CloseFile(handle);
|
|
|
|
|
|
|
|
|
|
ParamSFOData sfo;
|
|
|
|
|
sfo.ReadSFO((const u8 *)sfoData.data(), sfoData.size());
|
|
|
|
|
return sfo.GetValueString("DISC_ID");
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 21:55:17 +00:00
|
|
|
|
bool GameManager::ExtractFile(struct zip *z, int file_index, std::string outFilename, size_t *bytesCopied, size_t allBytes) {
|
|
|
|
|
struct zip_stat zstat;
|
|
|
|
|
zip_stat_index(z, file_index, 0, &zstat);
|
|
|
|
|
size_t size = zstat.size;
|
|
|
|
|
|
2019-07-08 21:03:27 +00:00
|
|
|
|
// Don't spam the log.
|
2019-07-07 21:55:17 +00:00
|
|
|
|
if (file_index < 10) {
|
2019-07-08 21:03:27 +00:00
|
|
|
|
INFO_LOG(HLE, "Writing %d bytes to '%s'", (int)size, outFilename.c_str());
|
2019-07-07 21:55:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
zip_file *zf = zip_fopen_index(z, file_index, 0);
|
2019-09-27 20:55:39 +00:00
|
|
|
|
if (!zf) {
|
|
|
|
|
ERROR_LOG(HLE, "Failed to open file by index (%d) (%s)", file_index, outFilename.c_str());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 21:55:17 +00:00
|
|
|
|
FILE *f = File::OpenCFile(outFilename, "wb");
|
|
|
|
|
if (f) {
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
const size_t blockSize = 1024 * 128;
|
|
|
|
|
u8 *buffer = new u8[blockSize];
|
|
|
|
|
while (pos < size) {
|
2019-07-14 09:32:32 +00:00
|
|
|
|
size_t readSize = std::min(blockSize, size - pos);
|
|
|
|
|
ssize_t retval = zip_fread(zf, buffer, readSize);
|
2019-07-14 22:07:47 +00:00
|
|
|
|
if (retval < 0 || (size_t)retval < readSize) {
|
2019-07-14 09:32:32 +00:00
|
|
|
|
ERROR_LOG(HLE, "Failed to read %d bytes from zip (%d) - archive corrupt?", (int)readSize, (int)retval);
|
2019-07-07 21:55:17 +00:00
|
|
|
|
delete[] buffer;
|
|
|
|
|
fclose(f);
|
|
|
|
|
zip_fclose(zf);
|
|
|
|
|
File::Delete(outFilename.c_str());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-07-14 09:32:32 +00:00
|
|
|
|
size_t written = fwrite(buffer, 1, readSize, f);
|
|
|
|
|
if (written != readSize) {
|
|
|
|
|
ERROR_LOG(HLE, "Wrote %d bytes out of %d - Disk full?", (int)written, (int)readSize);
|
|
|
|
|
delete[] buffer;
|
|
|
|
|
fclose(f);
|
|
|
|
|
zip_fclose(zf);
|
|
|
|
|
File::Delete(outFilename.c_str());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
pos += readSize;
|
2019-07-07 21:55:17 +00:00
|
|
|
|
|
2019-07-14 09:32:32 +00:00
|
|
|
|
*bytesCopied += readSize;
|
2019-07-07 21:55:17 +00:00
|
|
|
|
installProgress_ = (float)*bytesCopied / (float)allBytes;
|
|
|
|
|
}
|
|
|
|
|
zip_fclose(zf);
|
|
|
|
|
fclose(f);
|
|
|
|
|
delete[] buffer;
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
ERROR_LOG(HLE, "Failed to open file for writing");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-15 00:43:51 +00:00
|
|
|
|
bool GameManager::InstallMemstickGame(struct zip *z, const std::string &zipfile, const std::string &dest, const ZipFileInfo &info, bool allowRoot, bool deleteAfter) {
|
2019-07-07 21:55:17 +00:00
|
|
|
|
size_t allBytes = 0;
|
|
|
|
|
size_t bytesCopied = 0;
|
|
|
|
|
|
2020-01-26 18:43:18 +00:00
|
|
|
|
auto sy = GetI18NCategory("System");
|
2013-12-05 12:22:06 +00:00
|
|
|
|
|
2019-07-15 00:43:51 +00:00
|
|
|
|
auto fileAllowed = [&](const char *fn) {
|
|
|
|
|
if (!allowRoot && strchr(fn, '/') == 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const char *basefn = strrchr(fn, '/');
|
|
|
|
|
basefn = basefn ? basefn + 1 : fn;
|
|
|
|
|
|
|
|
|
|
if (info.ignoreMetaFiles) {
|
|
|
|
|
if (basefn[0] == '.' || !strcmp(basefn, "Thumbs.db") || !strcmp(basefn, "desktop.ini"))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
2019-07-07 21:55:17 +00:00
|
|
|
|
// Create all the directories first in one pass
|
2013-12-05 13:11:35 +00:00
|
|
|
|
std::set<std::string> createdDirs;
|
2019-07-15 00:43:51 +00:00
|
|
|
|
for (int i = 0; i < info.numFiles; i++) {
|
2013-11-20 13:42:48 +00:00
|
|
|
|
const char *fn = zip_get_name(z, i, 0);
|
2013-11-20 15:50:09 +00:00
|
|
|
|
std::string zippedName = fn;
|
2019-07-27 13:17:56 +00:00
|
|
|
|
if (zippedName.length() < (size_t)info.stripChars) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-07-15 00:43:51 +00:00
|
|
|
|
std::string outFilename = dest + zippedName.substr(info.stripChars);
|
2013-12-05 12:41:23 +00:00
|
|
|
|
bool isDir = *outFilename.rbegin() == '/';
|
2013-12-05 13:11:35 +00:00
|
|
|
|
if (!isDir && outFilename.find("/") != std::string::npos) {
|
|
|
|
|
outFilename = outFilename.substr(0, outFilename.rfind('/'));
|
|
|
|
|
}
|
|
|
|
|
if (createdDirs.find(outFilename) == createdDirs.end()) {
|
2013-11-20 13:42:48 +00:00
|
|
|
|
File::CreateFullPath(outFilename.c_str());
|
2013-12-05 13:11:35 +00:00
|
|
|
|
createdDirs.insert(outFilename);
|
|
|
|
|
}
|
2019-07-15 00:43:51 +00:00
|
|
|
|
if (!isDir && fileAllowed(fn)) {
|
2013-12-05 12:22:06 +00:00
|
|
|
|
struct zip_stat zstat;
|
|
|
|
|
if (zip_stat_index(z, i, 0, &zstat) >= 0) {
|
|
|
|
|
allBytes += zstat.size;
|
|
|
|
|
}
|
2013-11-20 13:42:48 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now, loop through again in a second pass, writing files.
|
2013-12-06 11:49:57 +00:00
|
|
|
|
std::vector<std::string> createdFiles;
|
2019-07-15 00:43:51 +00:00
|
|
|
|
for (int i = 0; i < info.numFiles; i++) {
|
2013-11-20 13:42:48 +00:00
|
|
|
|
const char *fn = zip_get_name(z, i, 0);
|
2013-11-20 15:36:58 +00:00
|
|
|
|
// Note that we do NOT write files that are not in a directory, to avoid random
|
2019-07-27 13:17:56 +00:00
|
|
|
|
// README files etc. (unless allowRoot is true.)
|
|
|
|
|
if (fileAllowed(fn) && strlen(fn) > (size_t)info.stripChars) {
|
2019-07-15 00:43:51 +00:00
|
|
|
|
fn += info.stripChars;
|
|
|
|
|
std::string outFilename = dest + fn;
|
2013-12-05 12:41:23 +00:00
|
|
|
|
bool isDir = *outFilename.rbegin() == '/';
|
2013-12-05 12:22:06 +00:00
|
|
|
|
if (isDir)
|
|
|
|
|
continue;
|
2013-12-05 12:01:00 +00:00
|
|
|
|
|
2019-07-07 21:55:17 +00:00
|
|
|
|
if (!ExtractFile(z, i, outFilename, &bytesCopied, allBytes)) {
|
|
|
|
|
goto bail;
|
2013-12-05 12:22:06 +00:00
|
|
|
|
} else {
|
2019-07-07 21:55:17 +00:00
|
|
|
|
createdFiles.push_back(outFilename);
|
2013-11-20 13:42:48 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-15 00:43:51 +00:00
|
|
|
|
INFO_LOG(HLE, "Extracted %i files (%i bytes / %i).", info.numFiles, (int)bytesCopied, (int)allBytes);
|
2013-11-20 13:42:48 +00:00
|
|
|
|
|
|
|
|
|
zip_close(z);
|
2019-07-15 00:43:51 +00:00
|
|
|
|
z = nullptr;
|
2013-12-05 12:01:00 +00:00
|
|
|
|
installProgress_ = 1.0f;
|
|
|
|
|
installInProgress_ = false;
|
2013-12-06 11:49:57 +00:00
|
|
|
|
installError_ = "";
|
2013-12-05 16:49:37 +00:00
|
|
|
|
if (deleteAfter) {
|
2015-09-22 21:02:02 +00:00
|
|
|
|
File::Delete(zipfile.c_str());
|
2013-12-05 16:49:37 +00:00
|
|
|
|
}
|
|
|
|
|
InstallDone();
|
2013-12-05 12:22:06 +00:00
|
|
|
|
return true;
|
2013-12-06 11:49:57 +00:00
|
|
|
|
|
|
|
|
|
bail:
|
|
|
|
|
// We end up here if disk is full or couldn't write to storage for some other reason.
|
2019-07-15 00:43:51 +00:00
|
|
|
|
zip_close(z);
|
2019-07-14 09:32:32 +00:00
|
|
|
|
// We don't delete the original in this case. Try to delete the files we created so far.
|
2013-12-06 11:49:57 +00:00
|
|
|
|
for (size_t i = 0; i < createdFiles.size(); i++) {
|
2015-09-22 21:02:02 +00:00
|
|
|
|
File::Delete(createdFiles[i].c_str());
|
2013-12-06 11:49:57 +00:00
|
|
|
|
}
|
|
|
|
|
for (auto iter = createdDirs.begin(); iter != createdDirs.end(); ++iter) {
|
2015-09-22 21:02:02 +00:00
|
|
|
|
File::DeleteDir(iter->c_str());
|
2013-12-06 11:49:57 +00:00
|
|
|
|
}
|
2019-07-15 00:43:51 +00:00
|
|
|
|
SetInstallError(sy->T("Storage full"));
|
2013-12-06 11:49:57 +00:00
|
|
|
|
return false;
|
2013-11-20 13:42:48 +00:00
|
|
|
|
}
|
2013-12-05 13:11:35 +00:00
|
|
|
|
|
2019-07-07 21:12:26 +00:00
|
|
|
|
bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, std::string zipfile, bool deleteAfter) {
|
2019-07-07 21:55:17 +00:00
|
|
|
|
// Let's place the output file in the currently selected Games directory.
|
2019-07-07 21:12:26 +00:00
|
|
|
|
|
2019-07-07 21:55:17 +00:00
|
|
|
|
std::string fn = zip_get_name(z, isoFileIndex, 0);
|
|
|
|
|
size_t nameOffset = fn.rfind('/');
|
|
|
|
|
if (nameOffset == std::string::npos) {
|
|
|
|
|
nameOffset = 0;
|
|
|
|
|
} else {
|
|
|
|
|
nameOffset++;
|
|
|
|
|
}
|
|
|
|
|
size_t allBytes = 1;
|
|
|
|
|
struct zip_stat zstat;
|
|
|
|
|
if (zip_stat_index(z, isoFileIndex, 0, &zstat) >= 0) {
|
|
|
|
|
allBytes += zstat.size;
|
|
|
|
|
}
|
2019-07-07 21:12:26 +00:00
|
|
|
|
|
2019-07-07 21:55:17 +00:00
|
|
|
|
std::string outputISOFilename = g_Config.currentDirectory + "/" + fn.substr(nameOffset);
|
|
|
|
|
size_t bytesCopied = 0;
|
|
|
|
|
if (ExtractFile(z, isoFileIndex, outputISOFilename, &bytesCopied, allBytes)) {
|
2020-08-15 13:51:41 +00:00
|
|
|
|
INFO_LOG(IO, "Successfully extracted ISO file to '%s'", outputISOFilename.c_str());
|
2019-07-07 21:55:17 +00:00
|
|
|
|
}
|
2019-07-07 21:12:26 +00:00
|
|
|
|
zip_close(z);
|
|
|
|
|
if (deleteAfter) {
|
|
|
|
|
File::Delete(zipfile.c_str());
|
|
|
|
|
}
|
2019-07-07 21:55:17 +00:00
|
|
|
|
|
|
|
|
|
z = 0;
|
|
|
|
|
installProgress_ = 1.0f;
|
|
|
|
|
installInProgress_ = false;
|
|
|
|
|
installError_ = "";
|
2019-07-07 21:12:26 +00:00
|
|
|
|
InstallDone();
|
2019-07-07 21:55:17 +00:00
|
|
|
|
return true;
|
2019-07-07 21:12:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-10 21:37:10 +00:00
|
|
|
|
bool GameManager::InstallGameOnThread(std::string url, std::string fileName, bool deleteAfter) {
|
2013-12-05 13:11:35 +00:00
|
|
|
|
if (installInProgress_) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-07-10 21:37:10 +00:00
|
|
|
|
installThread_.reset(new std::thread(std::bind(&GameManager::InstallGame, this, url, fileName, deleteAfter)));
|
2013-12-05 13:11:35 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2013-12-05 16:49:37 +00:00
|
|
|
|
|
2019-07-14 09:32:32 +00:00
|
|
|
|
bool GameManager::InstallRawISO(const std::string &file, const std::string &originalName, bool deleteAfter) {
|
2019-07-08 21:03:27 +00:00
|
|
|
|
std::string destPath = g_Config.currentDirectory + "/" + originalName;
|
2019-07-10 21:37:10 +00:00
|
|
|
|
// TODO: To save disk space, we should probably attempt a move first.
|
|
|
|
|
if (File::Copy(file, destPath)) {
|
|
|
|
|
if (deleteAfter) {
|
|
|
|
|
File::Delete(file.c_str());
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-08 21:03:27 +00:00
|
|
|
|
installProgress_ = 1.0f;
|
|
|
|
|
installInProgress_ = false;
|
|
|
|
|
installError_ = "";
|
|
|
|
|
InstallDone();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-05 16:49:37 +00:00
|
|
|
|
void GameManager::InstallDone() {
|
2019-09-28 18:30:43 +00:00
|
|
|
|
installDonePending_ = true;
|
2013-12-05 16:49:37 +00:00
|
|
|
|
}
|