mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-15 15:30:29 +02:00
The libretro core is the frontend, and the headless tools are the test and debugging hosts. The Windows, UWP, Android, iOS, macOS and SDL apps, the UI framework and imgui go, along with what only they used: the Win32 debugger hooks (g_frameMutex, the shutdown lock), JNI thread attachment, the Android text drawer, RAIntegration, Discord, recent files, game installs, tilt input, the audio resamplers, the control mapper, memstick moving and the remote ISO and upload server modes. The web server keeps the debugger. The MSVC, Gradle and ndk-build projects go too.
408 lines
11 KiB
C++
408 lines
11 KiB
C++
#include "ppsspp_config.h"
|
|
|
|
#if PPSSPP_PLATFORM(WINDOWS)
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include "Common/CommonWindows.h"
|
|
#include <direct.h>
|
|
#if PPSSPP_PLATFORM(UWP)
|
|
#include <fileapifromapp.h>
|
|
#endif
|
|
#else
|
|
#include <strings.h>
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <set>
|
|
#include <cstdio>
|
|
#include <sys/stat.h>
|
|
#include <cctype>
|
|
#include <algorithm> // remove_if
|
|
|
|
#include "Common/Data/Encoding/Utf8.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/Net/URL.h"
|
|
#include "Common/File/DirListing.h"
|
|
#include "Common/File/FileUtil.h"
|
|
#include "Common/File/AndroidStorage.h"
|
|
#include "Common/TimeUtil.h"
|
|
#include "Common/Log.h"
|
|
|
|
#if !defined(__linux__) && !defined(_WIN32) && !defined(__QNX__)
|
|
#define stat64 stat
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBNX
|
|
// Far from optimal, but I guess it works...
|
|
#define fseeko fseek
|
|
#define ftello ftell
|
|
#define fileno
|
|
#endif // HAVE_LIBNX
|
|
|
|
constexpr bool LOG_IO = false;
|
|
|
|
namespace File {
|
|
|
|
#if PPSSPP_PLATFORM(WINDOWS)
|
|
static uint64_t FiletimeToStatTime(FILETIME ft, int* out_usec) {
|
|
constexpr int64_t windowsTickResolution = 10000000;
|
|
constexpr int64_t secToUnixEpoch = 11644473600LL;
|
|
|
|
const int64_t ticks = ((uint64_t)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
|
|
|
|
if (out_usec) {
|
|
// The remainder will be between 0 and 9,999,999 ticks.
|
|
// Dividing by 10 gives 0 to 999,999 microseconds.
|
|
*out_usec = (int)((ticks % windowsTickResolution) / 10);
|
|
}
|
|
|
|
return (uint64_t)(ticks / windowsTickResolution - secToUnixEpoch);
|
|
}
|
|
|
|
// This is used for both WIN32_FILE_ATTRIBUTE_DATA and WIN32_FIND_DATA, which have the same time and size fields.
|
|
template<class T>
|
|
void ReadFileAttributes(FileInfo *fileInfo, const T &attrs) {
|
|
fileInfo->atime = FiletimeToStatTime(attrs.ftLastAccessTime, &fileInfo->atimeUs);
|
|
fileInfo->mtime = FiletimeToStatTime(attrs.ftLastWriteTime, &fileInfo->mtimeUs);
|
|
fileInfo->ctime = FiletimeToStatTime(attrs.ftCreationTime, &fileInfo->ctimeUs);
|
|
|
|
fileInfo->exists = true;
|
|
fileInfo->size = ((uint64_t)attrs.nFileSizeHigh << 32) | attrs.nFileSizeLow;
|
|
fileInfo->isDirectory = (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
fileInfo->isWritable = (attrs.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0;
|
|
|
|
if (attrs.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
|
|
fileInfo->access = 0444; // Read
|
|
} else {
|
|
fileInfo->access = 0666; // Read/Write
|
|
}
|
|
if (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
fileInfo->access |= 0111; // Execute
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
bool GetFileInfo(const Path &path, FileInfo * fileInfo) {
|
|
if (LOG_IO) {
|
|
INFO_LOG(Log::IO, "GetFileInfo %s", path.ToVisualString().c_str());
|
|
}
|
|
|
|
switch (path.Type()) {
|
|
case PathType::NATIVE:
|
|
break; // OK
|
|
case PathType::CONTENT_URI:
|
|
return Android_GetFileInfo(path.ToString(), fileInfo);
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
// TODO: Expand relative paths?
|
|
fileInfo->fullName = path;
|
|
|
|
#if PPSSPP_PLATFORM(WINDOWS)
|
|
WIN32_FILE_ATTRIBUTE_DATA attrs;
|
|
#if PPSSPP_PLATFORM(UWP)
|
|
if (!GetFileAttributesExFromAppW(path.ToWString().c_str(), GetFileExInfoStandard, &attrs)) {
|
|
#else
|
|
if (!GetFileAttributesExW(path.ToWString().c_str(), GetFileExInfoStandard, &attrs)) {
|
|
#endif
|
|
fileInfo->size = 0;
|
|
fileInfo->isDirectory = false;
|
|
fileInfo->exists = false;
|
|
return false;
|
|
}
|
|
|
|
ReadFileAttributes(fileInfo, attrs);
|
|
|
|
#else
|
|
|
|
#if (defined __ANDROID__) && (__ANDROID_API__ < 21)
|
|
struct stat file_info;
|
|
int result = stat(path.c_str(), &file_info);
|
|
#else
|
|
struct stat64 file_info;
|
|
int result = stat64(path.c_str(), &file_info);
|
|
#endif
|
|
if (result < 0) {
|
|
fileInfo->exists = false;
|
|
return false;
|
|
}
|
|
|
|
fileInfo->isDirectory = S_ISDIR(file_info.st_mode);
|
|
fileInfo->isWritable = false;
|
|
fileInfo->size = file_info.st_size;
|
|
fileInfo->exists = true;
|
|
fileInfo->atime = file_info.st_atime;
|
|
fileInfo->mtime = file_info.st_mtime;
|
|
fileInfo->ctime = file_info.st_ctime;
|
|
|
|
// TODO: Use statx to provide higher precision timestamps on Linux.
|
|
fileInfo->atimeUs = 0;
|
|
fileInfo->mtimeUs = 0;
|
|
fileInfo->ctimeUs = 0;
|
|
|
|
fileInfo->access = file_info.st_mode & 0x1ff;
|
|
// HACK: approximation
|
|
if (file_info.st_mode & 0200) {
|
|
fileInfo->isWritable = true;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool GetModifTimeT(const Path &filename, time_t *return_time) {
|
|
FileInfo info;
|
|
if (GetFileInfo(filename, &info)) {
|
|
*return_time = info.mtime;
|
|
return true;
|
|
} else {
|
|
*return_time = 0;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool GetModifTime(const Path &filename, tm & return_time) {
|
|
memset(&return_time, 0, sizeof(return_time));
|
|
FileInfo info;
|
|
if (GetFileInfo(filename, &info)) {
|
|
time_t t = info.mtime;
|
|
localtime_r((time_t*)&t, &return_time);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FileInfo::operator <(const FileInfo & other) const {
|
|
if (isDirectory && !other.isDirectory)
|
|
return true;
|
|
else if (!isDirectory && other.isDirectory)
|
|
return false;
|
|
if (strcasecmp(name.c_str(), other.name.c_str()) < 0)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
std::vector<File::FileInfo> ApplyFilter(std::vector<File::FileInfo> files, const char *extensionFilter, std::string_view prefix) {
|
|
std::set<std::string> filters;
|
|
if (extensionFilter) {
|
|
std::string tmp;
|
|
while (*extensionFilter) {
|
|
if (*extensionFilter == ':') {
|
|
filters.emplace("." + tmp);
|
|
tmp.clear();
|
|
} else {
|
|
tmp.push_back(*extensionFilter);
|
|
}
|
|
extensionFilter++;
|
|
}
|
|
if (!tmp.empty())
|
|
filters.emplace("." + tmp);
|
|
}
|
|
|
|
auto pred = [&](const File::FileInfo &info) {
|
|
// WARNING: Keep in mind that if we return true here, the files is REMOVED from the list.
|
|
// It's not retain_if.
|
|
if (!startsWith(info.name, prefix)) {
|
|
return true;
|
|
}
|
|
if (info.isDirectory || !extensionFilter)
|
|
return false;
|
|
std::string ext = info.fullName.GetFileExtension();
|
|
return filters.find(ext) == filters.end();
|
|
};
|
|
files.erase(std::remove_if(files.begin(), files.end(), pred), files.end());
|
|
return files;
|
|
}
|
|
|
|
bool GetFilesInDir(const Path &directory, std::vector<FileInfo> *files, const char *filter, int flags, std::string_view prefix) {
|
|
if (LOG_IO) {
|
|
INFO_LOG(Log::IO, "GetFilesInDir '%s' (ext %s, prefix %.*s)", directory.ToVisualString().c_str(), filter, (int)prefix.size(), prefix.data());
|
|
}
|
|
|
|
if (directory.Type() == PathType::CONTENT_URI) {
|
|
bool exists = false;
|
|
// TODO: Move prefix filtering over to the Java side for more speed.
|
|
std::vector<File::FileInfo> fileList = Android_ListContentUri(directory.ToString(), std::string(prefix), &exists);
|
|
int beforeFilter = (int)fileList.size();
|
|
*files = ApplyFilter(fileList, filter, prefix);
|
|
std::sort(files->begin(), files->end());
|
|
DEBUG_LOG(Log::IO, "GetFilesInDir: Found %d entries (%d before filter). Path: %s", (int)files->size(), beforeFilter, directory.ToVisualString().c_str());
|
|
return exists;
|
|
}
|
|
|
|
std::set<std::string> filters;
|
|
if (filter) {
|
|
std::string tmp;
|
|
while (*filter) {
|
|
if (*filter == ':') {
|
|
filters.insert(tmp);
|
|
tmp.clear();
|
|
} else {
|
|
tmp.push_back(*filter);
|
|
}
|
|
filter++;
|
|
}
|
|
if (!tmp.empty())
|
|
filters.insert(tmp);
|
|
}
|
|
|
|
#if PPSSPP_PLATFORM(WINDOWS)
|
|
if (directory.IsRoot()) {
|
|
// Special path that means root of file system.
|
|
// This does not respect prefix filtering.
|
|
std::vector<std::string> drives = File::GetWindowsDrives();
|
|
for (auto drive = drives.begin(); drive != drives.end(); ++drive) {
|
|
if (*drive == "A:/" || *drive == "B:/")
|
|
continue;
|
|
File::FileInfo fake;
|
|
fake.fullName = Path(*drive);
|
|
fake.name = *drive;
|
|
fake.isDirectory = true;
|
|
fake.exists = true;
|
|
fake.size = 0;
|
|
fake.isWritable = false;
|
|
files->push_back(fake);
|
|
}
|
|
return files->size();
|
|
}
|
|
// Find the first file in the directory.
|
|
WIN32_FIND_DATA ffd;
|
|
std::wstring wpath = directory.ToWString();
|
|
wpath += L"\\*";
|
|
#if PPSSPP_PLATFORM(UWP)
|
|
HANDLE hFind = FindFirstFileExFromAppW(wpath.c_str(), FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, 0);
|
|
#else
|
|
HANDLE hFind = FindFirstFileEx(wpath.c_str(), FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, 0);
|
|
#endif
|
|
if (hFind == INVALID_HANDLE_VALUE) {
|
|
return false;
|
|
}
|
|
do {
|
|
const std::string virtualName = ConvertWStringToUTF8(ffd.cFileName);
|
|
// check for "." and ".."
|
|
if (!(flags & GETFILES_GET_NAVIGATION_ENTRIES) && (virtualName == "." || virtualName == ".."))
|
|
continue;
|
|
// Remove dotfiles (optional with flag.)
|
|
if (!(flags & GETFILES_GETHIDDEN)) {
|
|
if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0)
|
|
continue;
|
|
}
|
|
|
|
if (!startsWith(virtualName, prefix)) {
|
|
continue;
|
|
}
|
|
|
|
if (LOG_IO) {
|
|
// INFO_LOG(Log::IO, "GetFilesInDir item %s", virtualName.c_str());
|
|
}
|
|
|
|
FileInfo info;
|
|
info.name = virtualName;
|
|
info.fullName = directory / virtualName;
|
|
|
|
ReadFileAttributes(&info, ffd);
|
|
|
|
if (!info.isDirectory) {
|
|
std::string ext = info.fullName.GetFileExtension();
|
|
if (!ext.empty()) {
|
|
ext = ext.substr(1); // Remove the dot.
|
|
if (filter && filters.find(ext) == filters.end()) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
files->push_back(info);
|
|
} while (FindNextFile(hFind, &ffd) != 0);
|
|
FindClose(hFind);
|
|
#else
|
|
struct dirent *result = NULL;
|
|
DIR *dirp = opendir(directory.c_str());
|
|
if (!dirp)
|
|
return false;
|
|
while ((result = readdir(dirp))) {
|
|
const std::string virtualName(result->d_name);
|
|
// check for "." and ".."
|
|
if (!(flags & GETFILES_GET_NAVIGATION_ENTRIES) && (virtualName == "." || virtualName == ".."))
|
|
continue;
|
|
|
|
// Remove dotfiles (optional with flag.)
|
|
if (!(flags & GETFILES_GETHIDDEN)) {
|
|
if (virtualName[0] == '.')
|
|
continue;
|
|
}
|
|
|
|
if (!startsWith(virtualName, prefix)) {
|
|
continue;
|
|
}
|
|
|
|
// Let's just reuse GetFileInfo. We're calling stat anyway to get isDirectory information.
|
|
Path fullName = directory / virtualName;
|
|
|
|
FileInfo info;
|
|
info.name = virtualName;
|
|
if (!GetFileInfo(fullName, &info)) {
|
|
continue;
|
|
}
|
|
if (!info.isDirectory) {
|
|
std::string ext = info.fullName.GetFileExtension();
|
|
if (!ext.empty()) {
|
|
ext = ext.substr(1); // Remove the dot.
|
|
if (filter && filters.find(ext) == filters.end()) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
files->push_back(info);
|
|
}
|
|
closedir(dirp);
|
|
#endif
|
|
std::sort(files->begin(), files->end());
|
|
if (LOG_IO) {
|
|
INFO_LOG(Log::IO, "GetFilesInDir: Found %d files", (int)files->size());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#if PPSSPP_PLATFORM(WINDOWS)
|
|
// Returns a vector with the device names
|
|
std::vector<std::string> GetWindowsDrives() {
|
|
std::vector<std::string> drives;
|
|
|
|
#if PPSSPP_PLATFORM(UWP)
|
|
DWORD logicaldrives = GetLogicalDrives();
|
|
for (int i = 0; i < 26; i++)
|
|
{
|
|
if (logicaldrives & (1 << i))
|
|
{
|
|
CHAR driveName[] = { (CHAR)(TEXT('A') + i), TEXT(':'), TEXT('\\'), TEXT('\0') };
|
|
std::string str(driveName);
|
|
drives.push_back(driveName);
|
|
}
|
|
}
|
|
return drives;
|
|
#else
|
|
const DWORD buffsize = GetLogicalDriveStrings(0, NULL);
|
|
std::vector<wchar_t> buff(buffsize);
|
|
if (GetLogicalDriveStrings(buffsize, buff.data()) == buffsize - 1) {
|
|
auto drive = buff.data();
|
|
while (*drive) {
|
|
std::string str(ConvertWStringToUTF8(drive));
|
|
str.pop_back(); // we don't want the final backslash
|
|
str += "/";
|
|
drives.push_back(str);
|
|
// advance to next drive
|
|
while (*drive++) {}
|
|
}
|
|
}
|
|
return drives;
|
|
#endif // PPSSPP_PLATFORM(UWP)
|
|
}
|
|
#endif // PPSSPP_PLATFORM(WINDOWS)
|
|
|
|
} // namespace File
|