// Copyright (c) 2012- 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/. #include #include #include #include #include "Common/Data/Color/RGBAUtil.h" #include "Common/Data/Text/I18n.h" #include "Common/Data/Text/Parsers.h" #include "Common/File/FileUtil.h" #include "Common/Log.h" #include "Common/StringUtils.h" #include "Common/System/OSD.h" #include "Common/Thread/ThreadUtil.h" #include "Common/TimeUtil.h" #include "Common/UI/Context.h" #include "Core/Config.h" #include "Core/System.h" #include "Core/Util/RecentFiles.h" #include "UI/BackgroundAudio.h" #include "UI/GameInfoCache.h" #include "UI/GamePacker.h" namespace GamePacker { namespace { struct Job { Path gamePath; // As the game list knows it, maybe a PSP/GAME/. Path file; // The image itself. Path finalOutput; // Where the converted game ends up. Path writeOutput; // What gets written: finalOutput, or a temporary name when that's the original's. bool replace = false; // Delete the original when done. bool toISO = false; CSOCompress::Preset preset = CSOCompress::Preset::Best; std::atomic progress{0.0f}; double startTime = 0.0; std::atomic cancel{false}; std::atomic done{false}; bool success = false; // Set before done. bool cancelled = false; // Set before done. std::string error; // Set before done. std::thread thread; }; // Only touched on the UI thread. The worker gets the raw pointer, and the job lives until it's joined. std::unique_ptr g_job; int g_generation = 0; Result g_lastResult; void RunJob(Job *job) { SetCurrentThreadName("GamePacker"); INFO_LOG(Log::Loader, "GamePacker: %s %s -> %s", job->toISO ? "Extracting" : "Compressing", job->file.c_str(), job->writeOutput.c_str()); auto progress = [job](float p) { job->progress = p; return !job->cancel; }; std::string error; bool cancelled = false; bool success; if (job->toISO) { success = CSOCompress::Extract(job->file, job->writeOutput, progress, &error, &cancelled); } else { success = CSOCompress::Compress(job->file, job->writeOutput, job->preset, progress, &error, &cancelled); } if (!success && !cancelled) { ERROR_LOG(Log::Loader, "GamePacker: Failed on %s: %s", job->file.c_str(), error.c_str()); } job->success = success; job->cancelled = cancelled; job->error = error; job->done = true; } // "" for ".", or for an EBOOT.PBP the game's disc id. std::string OutputBaseName(const Path &gamePath, const Path &file) { std::string name = file.GetFilename(); const std::string ext = file.GetFileExtension(); if (ext.size() <= name.size()) { name.resize(name.size() - ext.size()); } if (equalsNoCase(file.GetFilename(), "EBOOT.PBP")) { std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath, GameInfoFlags::PARAM_SFO); if (info->Ready(GameInfoFlags::PARAM_SFO)) { const std::string id = SanitizeString(info->id, StringRestriction::FileName); if (!id.empty()) { return id; } } } return name; } // Where an option puts its result, before looking at what's already there. Path FinalOutput(const Path &gamePath, const Path &file, Container from, const Option &option) { const Container to = option.toISO ? Container::ISO : Container::CSO; if (to == from) { return file; } return file.NavigateUp() / (OutputBaseName(gamePath, file) + (option.toISO ? ".iso" : ".cso")); } // " (2).", " (3)."... for a name that's taken. Path NumberedName(const Path &path) { const std::string ext = path.GetFileExtension(); std::string name = path.GetFilename(); name.resize(name.size() - ext.size()); for (int i = 2; i < 1000; i++) { Path candidate = path.NavigateUp() / StringFromFormat("%s (%d)%s", name.c_str(), i, ext.c_str()); if (!File::Exists(candidate)) { return candidate; } } return Path(); } } // namespace Container DetectContainer(const Path &gamePath, Path *file) { // Content URIs are fine too (Android's storage), only remote games aren't. if (gamePath.Type() == PathType::HTTP) { return Container::None; } Path image = gamePath; if (File::IsDirectory(gamePath)) { image = gamePath / "EBOOT.PBP"; if (!File::Exists(image)) { return Container::None; } } const std::string ext = image.GetFileExtension(); Container container = Container::None; if (ext == ".iso") { container = Container::ISO; } else if (ext == ".cso") { container = Container::CSO; } else if (ext == ".chd") { container = Container::CHD; } else if (ext == ".pbp") { container = Container::PBP; } if (container != Container::None && file) { *file = image; } return container; } std::vector