mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-20 19:38:31 +02:00
Cashing in the io/stat and io/shortname recordings. __IoGetStat began with memset(stat, 0xfe, sizeof(SceIoStat)), which destroyed 24 bytes of the caller's buffer that a real PSP never touches - it writes only as far as the timestamps and leaves all six st_private words exactly as it found them. It also wrote a made-up sector number into st_private[0] on the memory stick. That word carries the LBN on a UMD, which games read to build disc0:/sce_lbn paths, so it stays for non-FAT and is left alone otherwise. FAT has no permissions of its own and everything reads back as 0777. We were passing the host's idea of the file through instead. The existing "all files look executable on FAT" hack for Beats (issue #14812) was right in substance but lived only in sceIoDread, so sceIoGetstat and sceIoDread disagreed about the same file where hardware has them agree. Both now go through one path, which also gets the read-only case right: no write bits means mode 0555 and attr 0x21. sceIoGetstat on the root of a volume is refused, as on hardware. sceIoChstat was a logging stub. It now applies the read-only flag, which is what st_mode's write bits and st_attr's 0x01 both mean on FAT - setting either produces both, and it's reversible. That needs a new IFileSystem::SetFileWritable, defaulting to "can't" so read-only filesystems and hosts that can't express it (Android content URIs) are unaffected; the call still succeeds there, since hardware would have. GenerateFatShortNames now accounts for capitalisation. FAT keeps a lowercase flag for the base and another for the extension, but the PSP only honours the base one, so "shrt" becomes SHRT while "readme.txt" becomes README~1.TXT. We were only adding a counter on collision. The unit test carries the whole recorded set, including the corrected README~1.MD. io/shortname stays in tests_next: its d_name column can't match while SimulateVFATBug is uppercasing lowercase 8.3 names, which is deliberate and load-bearing for homebrew.
742 lines
21 KiB
C++
742 lines
21 KiB
C++
// 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 "Common/Serialize/Serializer.h"
|
|
#include "Common/Serialize/SerializeFuncs.h"
|
|
#include "Common/Serialize/SerializeMap.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Core/FileSystems/MetaFileSystem.h"
|
|
#include "Core/HLE/sceKernelThread.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/System.h"
|
|
|
|
static bool ApplyPathStringToComponentsVector(std::vector<std::string> &vector, const std::string &pathString)
|
|
{
|
|
size_t len = pathString.length();
|
|
size_t start = 0;
|
|
|
|
while (start < len)
|
|
{
|
|
// TODO: This should only be done for ms0:/ etc.
|
|
size_t i = pathString.find_first_of("/\\", start);
|
|
if (i == std::string::npos)
|
|
i = len;
|
|
|
|
if (i > start)
|
|
{
|
|
std::string component = pathString.substr(start, i - start);
|
|
if (component != ".")
|
|
{
|
|
if (component == "..")
|
|
{
|
|
if (vector.size() != 0)
|
|
{
|
|
vector.pop_back();
|
|
}
|
|
else
|
|
{
|
|
// The PSP silently ignores attempts to .. to parent of root directory
|
|
WARN_LOG(Log::FileSystem, "RealPath: ignoring .. beyond root - root directory is its own parent: \"%s\"", pathString.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vector.push_back(component);
|
|
}
|
|
}
|
|
}
|
|
|
|
start = i + 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Changes relative paths to absolute, removes ".", "..", and trailing "/"
|
|
* "drive:./blah" is absolute (ignore the dot) and "/blah" is relative (because it's missing "drive:")
|
|
* babel (and possibly other games) use "/directoryThatDoesNotExist/../directoryThatExists/filename"
|
|
*/
|
|
static bool RealPath(const std::string ¤tDirectory, const std::string &inPath, std::string &outPath)
|
|
{
|
|
size_t inLen = inPath.length();
|
|
if (inLen == 0)
|
|
{
|
|
outPath = currentDirectory;
|
|
return true;
|
|
}
|
|
|
|
size_t inColon = inPath.find(':');
|
|
if (inColon + 1 == inLen)
|
|
{
|
|
// There's nothing after the colon, e.g. umd0: - this is perfectly valid.
|
|
outPath = inPath;
|
|
return true;
|
|
}
|
|
|
|
bool relative = (inColon == std::string::npos);
|
|
|
|
std::string prefix, inAfterColon;
|
|
std::vector<std::string> cmpnts; // path components
|
|
size_t outPathCapacityGuess = inPath.length();
|
|
|
|
if (relative)
|
|
{
|
|
size_t curDirLen = currentDirectory.length();
|
|
if (curDirLen == 0)
|
|
{
|
|
ERROR_LOG(Log::FileSystem, "RealPath: inPath \"%s\" is relative, but current directory is empty", inPath.c_str());
|
|
return false;
|
|
}
|
|
|
|
size_t curDirColon = currentDirectory.find(':');
|
|
if (curDirColon == std::string::npos)
|
|
{
|
|
ERROR_LOG(Log::FileSystem, "RealPath: inPath \"%s\" is relative, but current directory \"%s\" has no prefix", inPath.c_str(), currentDirectory.c_str());
|
|
return false;
|
|
}
|
|
if (curDirColon + 1 == curDirLen)
|
|
{
|
|
WARN_LOG(Log::FileSystem, "RealPath: inPath \"%s\" is relative, but current directory \"%s\" is all prefix and no path. Using \"/\" as path for current directory.", inPath.c_str(), currentDirectory.c_str());
|
|
}
|
|
else
|
|
{
|
|
const std::string curDirAfter = currentDirectory.substr(curDirColon + 1);
|
|
if (! ApplyPathStringToComponentsVector(cmpnts, curDirAfter) )
|
|
{
|
|
ERROR_LOG(Log::FileSystem,"RealPath: currentDirectory is not a valid path: \"%s\"", currentDirectory.c_str());
|
|
return false;
|
|
}
|
|
|
|
outPathCapacityGuess += curDirLen;
|
|
}
|
|
|
|
prefix = currentDirectory.substr(0, curDirColon + 1);
|
|
inAfterColon = inPath;
|
|
}
|
|
else
|
|
{
|
|
prefix = inPath.substr(0, inColon + 1);
|
|
inAfterColon = inPath.substr(inColon + 1);
|
|
|
|
// Special case: "disc0:" is different from "disc0:/", so keep track of the single slash.
|
|
if (inAfterColon == "/")
|
|
{
|
|
outPath = prefix + inAfterColon;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (! ApplyPathStringToComponentsVector(cmpnts, inAfterColon) )
|
|
{
|
|
WARN_LOG(Log::FileSystem, "RealPath: inPath is not a valid path: \"%s\"", inPath.c_str());
|
|
return false;
|
|
}
|
|
|
|
outPath.clear();
|
|
outPath.reserve(outPathCapacityGuess);
|
|
|
|
outPath.append(prefix);
|
|
|
|
size_t numCmpnts = cmpnts.size();
|
|
for (size_t i = 0; i < numCmpnts; i++)
|
|
{
|
|
outPath.append(1, '/');
|
|
outPath.append(cmpnts[i]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
IFileSystem *MetaFileSystem::GetHandleOwner(u32 handle) const
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
for (size_t i = 0; i < fileSystems.size(); i++)
|
|
{
|
|
if (fileSystems[i].system->OwnsHandle(handle))
|
|
return fileSystems[i].system.get();
|
|
}
|
|
|
|
// Not found
|
|
return nullptr;
|
|
}
|
|
|
|
std::string MetaFileSystem::GetCurrentDirForThread(int threadID) const {
|
|
auto iter = currentDir.find(threadID);
|
|
if (iter == currentDir.end()) {
|
|
return "";
|
|
}
|
|
return iter->second;
|
|
}
|
|
|
|
int MetaFileSystem::MapFilePath(std::string_view _inpath, std::string *outpath, MountPoint **system) {
|
|
int error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
std::string realpath;
|
|
|
|
std::string inpath(_inpath);
|
|
|
|
// "ms0:/file.txt" is equivalent to " ms0:/file.txt". Yes, really.
|
|
if (inpath.find(':') != inpath.npos) {
|
|
size_t offset = 0;
|
|
while (inpath[offset] == ' ') {
|
|
offset++;
|
|
}
|
|
if (offset > 0) {
|
|
inpath = inpath.substr(offset);
|
|
}
|
|
}
|
|
|
|
// Special handling: host0:command.txt (as seen in Super Monkey Ball Adventures, for example)
|
|
// appears to mean the current directory on the UMD.
|
|
// Actually, not sure if this is still needed. It doesn't make a whole lot of sense.
|
|
if (startsWithNoCase(inpath.c_str(), "host0:") && !host0Mapped_) {
|
|
inpath = "umd0:" + inpath.substr(strlen("host0:"));
|
|
}
|
|
|
|
const std::string *currentDirectory = &startingDirectory;
|
|
|
|
// Hm, does this make sense? Doesn't each drive has its own currentDir per thread, or maybe not?
|
|
int currentThread = __KernelGetCurThread();
|
|
auto it = currentDir.find(currentThread);
|
|
if (it == currentDir.end()) {
|
|
// Attempt to emulate SCE_KERNEL_ERROR_NOCWD / 8002032C: may break things requiring fixes elsewhere
|
|
if (inpath.find(':') == std::string::npos /* means path is relative */) {
|
|
error = SCE_KERNEL_ERROR_NOCWD;
|
|
WARN_LOG(Log::FileSystem, "Path is relative, but current directory not set for thread %i. returning 8002032C(SCE_KERNEL_ERROR_NOCWD) instead.", currentThread);
|
|
}
|
|
} else {
|
|
currentDirectory = &(it->second);
|
|
}
|
|
|
|
if (RealPath(*currentDirectory, inpath, realpath)) {
|
|
std::string prefix = realpath;
|
|
size_t prefixPos = realpath.find(':');
|
|
if (prefixPos != realpath.npos)
|
|
prefix = NormalizePrefix(realpath.substr(0, prefixPos + 1));
|
|
|
|
for (size_t i = 0; i < fileSystems.size(); i++) {
|
|
if (equalsNoCase(fileSystems[i].prefix, prefix)) {
|
|
// Map into the underlying filesystem. If the mount specifies a subDir,
|
|
// join that with the path inside the device.
|
|
std::string basePath = realpath.substr(prefixPos + 1); // may be empty or start with '/'
|
|
const std::string &mountSub = fileSystems[i].subDir;
|
|
if (mountSub.empty()) {
|
|
*outpath = basePath;
|
|
} else {
|
|
// Normalize subDir: ensure it starts with '/' and has no trailing slash (unless it's root "/").
|
|
std::string s = mountSub;
|
|
if (s.empty()) s = "/";
|
|
if (s[0] != '/') s.insert(s.begin(), '/');
|
|
if (s.size() > 1 && s.back() == '/') s.pop_back();
|
|
if (basePath.empty()) {
|
|
*outpath = s;
|
|
} else {
|
|
*outpath = s + basePath; // basePath usually starts with '/'
|
|
}
|
|
}
|
|
*system = &(fileSystems[i]);
|
|
|
|
VERBOSE_LOG(Log::FileSystem, "MapFilePath: mapped \"%s\" to prefix: \"%s\", path: \"%s\"", inpath.c_str(), fileSystems[i].prefix.c_str(), outpath->c_str());
|
|
|
|
return error == SCE_KERNEL_ERROR_NOCWD ? error : 0;
|
|
}
|
|
}
|
|
|
|
error = SCE_KERNEL_ERROR_NODEV;
|
|
}
|
|
|
|
DEBUG_LOG(Log::FileSystem, "MapFilePath: failed mapping \"%s\", returning false", inpath.c_str());
|
|
return error;
|
|
}
|
|
|
|
std::string_view MetaFileSystem::NormalizePrefix(std::string_view prefix) const {
|
|
// Let's apply some mapping here since it won't break savestates.
|
|
if (prefix == "memstick:")
|
|
return "ms0:";
|
|
// Seems like umd00: etc. work just fine... avoid umd1/umd for tests.
|
|
if (startsWith(prefix, "umd") && prefix != "umd1:" && prefix != "umd:")
|
|
return "umd0:";
|
|
// Seems like umd00: etc. work just fine...
|
|
if (startsWith(prefix, "host"))
|
|
return "host0:";
|
|
|
|
// Should we simply make this case insensitive?
|
|
if (prefix == "DISC0:")
|
|
return "disc0:";
|
|
return prefix;
|
|
}
|
|
|
|
void MetaFileSystem::Mount(std::string_view prefix, std::shared_ptr<IFileSystem> system, std::string_view subDir) {
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
for (auto &it : fileSystems) {
|
|
if (it.prefix == prefix) {
|
|
// Overwrite the old mount.
|
|
it.system = system;
|
|
it.subDir = std::string(subDir);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (equalsNoCase(prefix, "host0:")) {
|
|
host0Mapped_ = true;
|
|
}
|
|
|
|
// Prefix not yet mounted, do so.
|
|
MountPoint x;
|
|
x.prefix = prefix;
|
|
x.system = system;
|
|
x.subDir = std::string(subDir);
|
|
fileSystems.push_back(x);
|
|
}
|
|
|
|
// Assumes the lock is held
|
|
void MetaFileSystem::UnmountAll() {
|
|
fileSystems.clear();
|
|
currentDir.clear();
|
|
host0Mapped_ = false;
|
|
}
|
|
|
|
void MetaFileSystem::Unmount(std::string_view prefix) {
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
if (equalsNoCase(prefix, "host0:")) {
|
|
host0Mapped_ = false;
|
|
}
|
|
for (auto iter = fileSystems.begin(); iter != fileSystems.end(); iter++) {
|
|
if (iter->prefix == prefix) {
|
|
fileSystems.erase(iter);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
IFileSystem *MetaFileSystem::GetSystemFromFilename(std::string_view filename) {
|
|
size_t prefixPos = filename.find(':');
|
|
if (prefixPos == filename.npos)
|
|
return 0;
|
|
return GetSystem(filename.substr(0, prefixPos + 1));
|
|
}
|
|
|
|
IFileSystem *MetaFileSystem::GetSystem(std::string_view prefix) {
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
for (auto it = fileSystems.begin(); it != fileSystems.end(); ++it) {
|
|
if (it->prefix == NormalizePrefix(prefix))
|
|
return it->system.get();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void MetaFileSystem::Shutdown() {
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
|
|
UnmountAll();
|
|
Reset();
|
|
}
|
|
|
|
int MetaFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
std::string of;
|
|
MountPoint *mount;
|
|
int error = MapFilePath(filename, &of, &mount);
|
|
if (error == 0)
|
|
return mount->system->OpenFile(of, access, mount->prefix.c_str());
|
|
else
|
|
return error;
|
|
}
|
|
|
|
PSPFileInfo MetaFileSystem::GetFileInfo(std::string filename)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
std::string of;
|
|
IFileSystem *system;
|
|
int error = MapFilePath(filename, &of, &system);
|
|
if (error == 0)
|
|
{
|
|
return system->GetFileInfo(of);
|
|
}
|
|
else
|
|
{
|
|
PSPFileInfo bogus;
|
|
return bogus;
|
|
}
|
|
}
|
|
|
|
PSPFileInfo MetaFileSystem::GetFileInfoByHandle(u32 handle) {
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
IFileSystem *sys = GetHandleOwner(handle);
|
|
if (sys)
|
|
return sys->GetFileInfoByHandle(handle);
|
|
return PSPFileInfo();
|
|
}
|
|
|
|
std::vector<PSPFileInfo> MetaFileSystem::GetDirListing(std::string_view path, bool *exists) {
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
std::string of;
|
|
IFileSystem *system;
|
|
int error = MapFilePath(path, &of, &system);
|
|
if (error == 0) {
|
|
return system->GetDirListing(of, exists);
|
|
} else {
|
|
std::vector<PSPFileInfo> empty;
|
|
if (exists)
|
|
*exists = false;
|
|
return empty;
|
|
}
|
|
}
|
|
|
|
void MetaFileSystem::ThreadEnded(int threadID)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
currentDir.erase(threadID);
|
|
}
|
|
|
|
int MetaFileSystem::ChDir(const std::string &dir)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
// Retain the old path and fail if the arg is 1023 bytes or longer.
|
|
if (dir.size() >= 1023)
|
|
return SCE_KERNEL_ERROR_NAMETOOLONG;
|
|
|
|
int curThread = __KernelGetCurThread();
|
|
|
|
std::string of;
|
|
MountPoint *mountPoint;
|
|
int error = MapFilePath(dir, &of, &mountPoint);
|
|
if (error == 0)
|
|
{
|
|
currentDir[curThread] = mountPoint->prefix + of;
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
for (size_t i = 0; i < fileSystems.size(); i++)
|
|
{
|
|
const std::string &prefix = fileSystems[i].prefix;
|
|
if (strncasecmp(prefix.c_str(), dir.c_str(), prefix.size()) == 0)
|
|
{
|
|
// The PSP is completely happy with invalid current dirs as long as they have a valid device.
|
|
WARN_LOG(Log::FileSystem, "ChDir failed to map path \"%s\", saving as current directory anyway", dir.c_str());
|
|
currentDir[curThread] = dir;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
WARN_LOG_REPORT(Log::FileSystem, "ChDir failed to map device for \"%s\", failing", dir.c_str());
|
|
return SCE_KERNEL_ERROR_NODEV;
|
|
}
|
|
}
|
|
|
|
bool MetaFileSystem::MkDir(const std::string &dirname)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
std::string of;
|
|
IFileSystem *system;
|
|
int error = MapFilePath(dirname, &of, &system);
|
|
if (error == 0)
|
|
{
|
|
return system->MkDir(of);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool MetaFileSystem::RmDir(const std::string &dirname)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
std::string of;
|
|
IFileSystem *system;
|
|
int error = MapFilePath(dirname, &of, &system);
|
|
if (error == 0)
|
|
{
|
|
return system->RmDir(of);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int MetaFileSystem::RenameFile(const std::string &from, const std::string &to)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
std::string of;
|
|
std::string rf;
|
|
IFileSystem *osystem;
|
|
IFileSystem *rsystem = NULL;
|
|
int error = MapFilePath(from, &of, &osystem);
|
|
if (error == 0)
|
|
{
|
|
// If it's a relative path, it seems to always use from's filesystem.
|
|
if (to.find(":/") != to.npos)
|
|
{
|
|
error = MapFilePath(to, &rf, &rsystem);
|
|
if (error < 0)
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
rf = to;
|
|
rsystem = osystem;
|
|
}
|
|
|
|
if (osystem != rsystem)
|
|
return SCE_KERNEL_ERROR_XDEV;
|
|
|
|
return osystem->RenameFile(of, rf);
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
bool MetaFileSystem::SetFileWritable(const std::string &filename, bool writable)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
std::string of;
|
|
IFileSystem *system;
|
|
int error = MapFilePath(filename, &of, &system);
|
|
if (error == 0) {
|
|
return system->SetFileWritable(of, writable);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool MetaFileSystem::RemoveFile(const std::string &filename)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
std::string of;
|
|
IFileSystem *system;
|
|
int error = MapFilePath(filename, &of, &system);
|
|
if (error == 0) {
|
|
return system->RemoveFile(of);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int MetaFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
IFileSystem *sys = GetHandleOwner(handle);
|
|
if (sys)
|
|
return sys->Ioctl(handle, cmd, indataPtr, inlen, outdataPtr, outlen, usec);
|
|
return SCE_KERNEL_ERROR_ERROR;
|
|
}
|
|
|
|
PSPDevType MetaFileSystem::DevType(u32 handle)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
IFileSystem *sys = GetHandleOwner(handle);
|
|
if (sys)
|
|
return sys->DevType(handle);
|
|
return PSPDevType::INVALID;
|
|
}
|
|
|
|
void MetaFileSystem::CloseFile(u32 handle)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
IFileSystem *sys = GetHandleOwner(handle);
|
|
if (sys)
|
|
sys->CloseFile(handle);
|
|
}
|
|
|
|
size_t MetaFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
IFileSystem *sys = GetHandleOwner(handle);
|
|
if (sys)
|
|
return sys->ReadFile(handle, pointer, size);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
size_t MetaFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
IFileSystem *sys = GetHandleOwner(handle);
|
|
if (sys)
|
|
return sys->WriteFile(handle, pointer, size);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
size_t MetaFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
IFileSystem *sys = GetHandleOwner(handle);
|
|
if (sys)
|
|
return sys->ReadFile(handle, pointer, size, usec);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
size_t MetaFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
IFileSystem *sys = GetHandleOwner(handle);
|
|
if (sys)
|
|
return sys->WriteFile(handle, pointer, size, usec);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
size_t MetaFileSystem::SeekFile(u32 handle, s32 position, FileMove type)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
IFileSystem *sys = GetHandleOwner(handle);
|
|
if (sys)
|
|
return sys->SeekFile(handle, position, type);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int MetaFileSystem::ReadEntireFile(const std::string &filename, std::vector<u8> &data, bool quiet) {
|
|
FileAccess access = FILEACCESS_READ;
|
|
if (quiet) {
|
|
access = (FileAccess)(access | FILEACCESS_PPSSPP_QUIET);
|
|
}
|
|
int handle = OpenFile(filename, access);
|
|
if (handle < 0)
|
|
return handle;
|
|
|
|
SeekFile(handle, 0, FILEMOVE_END);
|
|
size_t dataSize = GetSeekPos(handle);
|
|
SeekFile(handle, 0, FILEMOVE_BEGIN);
|
|
data.resize(dataSize);
|
|
|
|
size_t result = ReadFile(handle, data.data(), dataSize);
|
|
CloseFile(handle);
|
|
|
|
if (result != dataSize)
|
|
return SCE_KERNEL_ERROR_ERROR;
|
|
|
|
return 0;
|
|
}
|
|
|
|
u64 MetaFileSystem::FreeDiskSpace(const std::string &path) {
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
std::string of;
|
|
IFileSystem *system;
|
|
int error = MapFilePath(path, &of, &system);
|
|
if (error == 0)
|
|
return system->FreeDiskSpace(of);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void MetaFileSystem::DoState(PointerWrap &p) {
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
|
|
auto s = p.Section("MetaFileSystem", 1);
|
|
if (!s)
|
|
return;
|
|
|
|
Do(p, current);
|
|
|
|
// Save/load per-thread current directory map
|
|
Do(p, currentDir);
|
|
|
|
u32 n = (u32) fileSystems.size();
|
|
Do(p, n);
|
|
|
|
// The mounts are serialized positionally: one section per mount, in fileSystems order, with no
|
|
// length to skip by. That order is the order Mount() first saw each prefix during boot, which
|
|
// spans more than MountFileSystems() - booting an ISO, MountGameISO() has already added umd0:,
|
|
// umd1:, umd: and disc0: by the time we get there. An older build's savestate simply lacks the
|
|
// sections for mounts that didn't exist yet, and we have to leave out exactly those to stay
|
|
// lined up. We only know how many are missing (n), not which, hence the list below.
|
|
//
|
|
// ADDING A NEW MOUNT: prepend its prefix here, or every existing savestate stops loading with
|
|
// "Failure at <whatever section follows>". Newest first, because a state missing k mounts is
|
|
// missing the k most recently added ones. Where the new mount falls in the order doesn't
|
|
// matter - skipping it by prefix leaves the relative order of all the others unchanged.
|
|
//
|
|
// DO NOT REORDER EXISTING MOUNTS. Sections are paired with filesystems purely by position, so
|
|
// swapping two Mount() calls feeds every old savestate's sections to the wrong filesystems,
|
|
// silently and with no version to catch it.
|
|
//
|
|
// RENAMING OR REMOVING A MOUNT isn't handled here either: a section in the state we have no
|
|
// mount for can't be skipped, because we can't know how long it is. Either of those needs a
|
|
// format change - storing the prefixes, or a length per section.
|
|
static const char * const mountsAddedOverTime[] = { "flash1:", "pfat0:" };
|
|
|
|
const size_t missing = n < (u32)fileSystems.size() ? fileSystems.size() - n : 0;
|
|
if (n > (u32)fileSystems.size() || missing > ARRAY_SIZE(mountsAddedOverTime)) {
|
|
p.SetError(p.ERROR_FAILURE);
|
|
ERROR_LOG(Log::FileSystem, "Savestate failure: number of filesystems doesn't match (%d in state, %d mounted).", (int)n, (int)fileSystems.size());
|
|
return;
|
|
}
|
|
|
|
for (const MountPoint &mount : fileSystems) {
|
|
bool skip = false;
|
|
for (size_t i = 0; i < missing; i++) {
|
|
if (mount.prefix == mountsAddedOverTime[i]) {
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!skip) {
|
|
mount.system->DoState(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
int64_t MetaFileSystem::RecursiveSize(std::string_view dirPath) {
|
|
u64 result = 0;
|
|
auto allFiles = GetDirListing(std::string(dirPath));
|
|
for (const auto &file : allFiles) {
|
|
if (file.name == "." || file.name == "..")
|
|
continue;
|
|
if (file.type == FILETYPE_DIRECTORY) {
|
|
result += RecursiveSize(join(dirPath, file.name));
|
|
} else {
|
|
result += file.size;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int64_t MetaFileSystem::ComputeRecursiveDirectorySize(std::string_view filename) {
|
|
std::lock_guard<std::recursive_mutex> guard(lock);
|
|
std::string of;
|
|
IFileSystem *system;
|
|
int error = MapFilePath(filename, &of, &system);
|
|
if (error == 0) {
|
|
int64_t size;
|
|
if (system->ComputeRecursiveDirSizeIfFast(of, &size)) {
|
|
// Some file systems can optimize this.
|
|
return size;
|
|
} else {
|
|
// Those that can't, we just run a generic implementation.
|
|
return RecursiveSize(filename);
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool MetaFileSystem::ComputeRecursiveDirSizeIfFast(const std::string &path, int64_t *size) {
|
|
// Shouldn't be called. Can't recurse MetaFileSystem.
|
|
_dbg_assert_(false);
|
|
return false;
|
|
}
|