From d0b69355926525b7010f2cae2e0b5586223bd0a2 Mon Sep 17 00:00:00 2001 From: Bashar Astifan Date: Fri, 28 Apr 2023 23:22:17 +0400 Subject: [PATCH] UWP Improved Support --- Common/File/DirListing.cpp | 24 + Common/File/FileUtil.cpp | 87 ++- Core/FileLoaders/LocalFileLoader.cpp | 19 + Core/FileSystems/DirectoryFileSystem.cpp | 18 + Core/FileSystems/VirtualDiscFileSystem.cpp | 38 + Core/PSPLoaders.cpp | 5 + Core/System.cpp | 3 + Core/Util/GameManager.cpp | 10 + UI/CwCheatScreen.cpp | 5 + UI/DevScreens.cpp | 15 +- UI/GameSettingsScreen.cpp | 9 + UI/MainScreen.cpp | 6 +- UI/MemStickScreen.cpp | 51 +- UI/MemStickScreen.h | 8 + UI/NativeApp.cpp | 37 +- UWP/App.cpp | 38 +- UWP/PPSSPP_UWPMain.cpp | 180 ++++- UWP/PPSSPP_UWPMain.h | 5 +- UWP/Package.appxmanifest | 17 + UWP/PackageGold.appxmanifest | 17 + UWP/PackageNormal.appxmanifest | 17 + UWP/UWP.vcxproj | 27 +- UWP/UWP.vcxproj.filters | 114 ++- UWP/UWPHelpers/LaunchItem.h | 115 +++ UWP/UWPHelpers/PPSSPPTypesHelpers.cpp | 85 ++ UWP/UWPHelpers/PPSSPPTypesHelpers.h | 19 + UWP/UWPHelpers/StorageAccess.cpp | 223 ++++++ UWP/UWPHelpers/StorageAccess.h | 22 + UWP/UWPHelpers/StorageAsync.cpp | 32 + UWP/UWPHelpers/StorageAsync.h | 106 +++ UWP/UWPHelpers/StorageConfig.h | 23 + UWP/UWPHelpers/StorageExtensions.cpp | 316 ++++++++ UWP/UWPHelpers/StorageExtensions.h | 93 +++ UWP/UWPHelpers/StorageFileW.h | 281 +++++++ UWP/UWPHelpers/StorageFolderW.h | 570 ++++++++++++++ UWP/UWPHelpers/StorageHandler.cpp | 132 ++++ UWP/UWPHelpers/StorageHandler.h | 137 ++++ UWP/UWPHelpers/StorageInfo.h | 24 + UWP/UWPHelpers/StorageItemW.h | 324 ++++++++ UWP/UWPHelpers/StorageLog.h | 17 + UWP/UWPHelpers/StorageManager.cpp | 869 +++++++++++++++++++++ UWP/UWPHelpers/StorageManager.h | 106 +++ UWP/UWPHelpers/StoragePath.cpp | 272 +++++++ UWP/UWPHelpers/StoragePath.h | 113 +++ UWP/UWPHelpers/StoragePickers.cpp | 80 ++ UWP/UWPHelpers/StoragePickers.h | 18 + UWP/UWPHelpers/UWP2C.cpp | 93 +++ UWP/UWPHelpers/UWP2C.h | 26 + ext/libzip/zip_source_file_win32_utf16.c | 42 +- 49 files changed, 4812 insertions(+), 76 deletions(-) create mode 100644 UWP/UWPHelpers/LaunchItem.h create mode 100644 UWP/UWPHelpers/PPSSPPTypesHelpers.cpp create mode 100644 UWP/UWPHelpers/PPSSPPTypesHelpers.h create mode 100644 UWP/UWPHelpers/StorageAccess.cpp create mode 100644 UWP/UWPHelpers/StorageAccess.h create mode 100644 UWP/UWPHelpers/StorageAsync.cpp create mode 100644 UWP/UWPHelpers/StorageAsync.h create mode 100644 UWP/UWPHelpers/StorageConfig.h create mode 100644 UWP/UWPHelpers/StorageExtensions.cpp create mode 100644 UWP/UWPHelpers/StorageExtensions.h create mode 100644 UWP/UWPHelpers/StorageFileW.h create mode 100644 UWP/UWPHelpers/StorageFolderW.h create mode 100644 UWP/UWPHelpers/StorageHandler.cpp create mode 100644 UWP/UWPHelpers/StorageHandler.h create mode 100644 UWP/UWPHelpers/StorageInfo.h create mode 100644 UWP/UWPHelpers/StorageItemW.h create mode 100644 UWP/UWPHelpers/StorageLog.h create mode 100644 UWP/UWPHelpers/StorageManager.cpp create mode 100644 UWP/UWPHelpers/StorageManager.h create mode 100644 UWP/UWPHelpers/StoragePath.cpp create mode 100644 UWP/UWPHelpers/StoragePath.h create mode 100644 UWP/UWPHelpers/StoragePickers.cpp create mode 100644 UWP/UWPHelpers/StoragePickers.h create mode 100644 UWP/UWPHelpers/UWP2C.cpp create mode 100644 UWP/UWPHelpers/UWP2C.h diff --git a/Common/File/DirListing.cpp b/Common/File/DirListing.cpp index b793e85057..b6fe3332a9 100644 --- a/Common/File/DirListing.cpp +++ b/Common/File/DirListing.cpp @@ -6,6 +6,10 @@ #include #if PPSSPP_PLATFORM(UWP) #include +#if !defined(__LIBRETRO__) +#include "UWP/UWPHelpers/StorageManager.h" +#include "UWP/UWPHelpers/PPSSPPTypesHelpers.h" +#endif #endif #else #include @@ -67,6 +71,11 @@ bool GetFileInfo(const Path &path, FileInfo * fileInfo) { WIN32_FILE_ATTRIBUTE_DATA attrs; #if PPSSPP_PLATFORM(UWP) if (!GetFileAttributesExFromAppW(path.ToWString().c_str(), GetFileExInfoStandard, &attrs)) { +#if !defined(__LIBRETRO__) + if (GetFileInfoUWP(path.ToString(), fileInfo)) { + return true; + } +#endif #else if (!GetFileAttributesExW(path.ToWString().c_str(), GetFileExInfoStandard, &attrs)) { #endif @@ -220,6 +229,15 @@ bool GetFilesInDir(const Path &directory, std::vector *files, const ch HANDLE hFind = FindFirstFileEx((directory.ToWString() + L"\\*").c_str(), FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, 0); #endif if (hFind == INVALID_HANDLE_VALUE) { +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + // Getting folder contents need extra work + // request must be done within StorageManager + auto contents = GetFolderContents(directory.ToString()); + if (ItemsInfoUWPToFilesInfo(contents, files, filter, filters)) { + std::sort(files->begin(), files->end()); + return true; + } +#endif return false; } do { @@ -319,7 +337,13 @@ std::vector GetWindowsDrives() { CHAR driveName[] = { (CHAR)(TEXT('A') + i), TEXT(':'), TEXT('\\'), TEXT('\0') }; std::string str(driveName); +#if !defined(__LIBRETRO__) + if (CheckDriveAccess(driveName, true)) { + drives.push_back(driveName); + } +#else drives.push_back(driveName); +#endif } } return drives; diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp index e8088e90bc..fd65b62ced 100644 --- a/Common/File/FileUtil.cpp +++ b/Common/File/FileUtil.cpp @@ -53,6 +53,9 @@ #include // getcwd #if PPSSPP_PLATFORM(UWP) #include +#if !defined(__LIBRETRO__) +#include "UWP/UWPHelpers/StorageManager.h" +#endif #endif #else #include @@ -157,7 +160,18 @@ FILE *OpenCFile(const Path &path, const char *mode) { } #if defined(_WIN32) && defined(UNICODE) +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + // We shouldn't use _wfopen specificly for custom memory stick location + FILE* file = GetFileStreamFromApp(path.ToString(), mode); + + if (!file) { + // Call UWP storage helper + file = GetFileStream(path.ToString(), mode); + } + return file; +#else return _wfopen(path.ToWString().c_str(), ConvertUTF8ToWString(mode).c_str()); +#endif #else return fopen(path.c_str(), mode); #endif @@ -258,6 +272,10 @@ static bool ResolvePathVista(const std::wstring &path, wchar_t *buf, DWORD bufSi if (getFinalPathNameByHandleW) { #if PPSSPP_PLATFORM(UWP) HANDLE hFile = CreateFile2FromAppW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr); +#if !defined(__LIBRETRO__) + //Use UWP StorageManager to get handle + hFile = CreateFileUWP(path, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING); +#endif #else HANDLE hFile = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); #endif @@ -373,6 +391,11 @@ bool Exists(const Path &path) { WIN32_FILE_ATTRIBUTE_DATA data{}; #if PPSSPP_PLATFORM(UWP) if (!GetFileAttributesExFromAppW(path.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) { +#if !defined(__LIBRETRO__) + if (IsExistsUWP(path.ToString())) { + return true; + } +#endif return false; } #else @@ -411,6 +434,11 @@ bool IsDirectory(const Path &filename) { WIN32_FILE_ATTRIBUTE_DATA data{}; #if PPSSPP_PLATFORM(UWP) if (!GetFileAttributesExFromAppW(filename.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) { +#if !defined(__LIBRETRO__) + if (IsDirectoryUWP(filename.ToString())) { + return true; + } +#endif #else if (!GetFileAttributesEx(filename.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) { #endif @@ -464,6 +492,11 @@ bool Delete(const Path &filename) { #ifdef _WIN32 #if PPSSPP_PLATFORM(UWP) if (!DeleteFileFromAppW(filename.ToWString().c_str())) { +#if !defined(__LIBRETRO__) + if (DeleteUWP(filename.ToString())) { + return true; + } +#endif WARN_LOG(COMMON, "Delete: DeleteFile failed on %s: %s", filename.c_str(), GetLastErrorMsg().c_str()); return false; } @@ -518,8 +551,19 @@ bool CreateDir(const Path &path) { DEBUG_LOG(COMMON, "CreateDir('%s')", path.c_str()); #ifdef _WIN32 #if PPSSPP_PLATFORM(UWP) - if (CreateDirectoryFromAppW(path.ToWString().c_str(), NULL)) + if (CreateDirectoryFromAppW(path.ToWString().c_str(), NULL)) { return true; + } + else { +#if !defined(__LIBRETRO__) + if (File::Exists(path)) { + return true; + } + else { + CreateDirectoryUWP(path.ToString()); + } +#endif + } #else if (::CreateDirectory(path.ToWString().c_str(), NULL)) return true; @@ -613,8 +657,16 @@ bool DeleteDir(const Path &path) { #ifdef _WIN32 #if PPSSPP_PLATFORM(UWP) - if (RemoveDirectoryFromAppW(path.ToWString().c_str())) + if (RemoveDirectoryFromAppW(path.ToWString().c_str())) { return true; + } + else { +#if !defined(__LIBRETRO__) + if (DeleteUWP(path.ToString())) { + return true; + } +#endif + } #else if (::RemoveDirectory(path.ToWString().c_str())) return true; @@ -662,6 +714,13 @@ bool Rename(const Path &srcFilename, const Path &destFilename) { std::wstring destw = destFilename.ToWString(); if (_wrename(srcw.c_str(), destw.c_str()) == 0) return true; +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + else { + if (RenameUWP(srcFilename.ToString(), destFilename.ToString())) { + return true; + } +} +#endif #else if (rename(srcFilename.c_str(), destFilename.c_str()) == 0) return true; @@ -696,6 +755,13 @@ bool Copy(const Path &srcFilename, const Path &destFilename) { #if PPSSPP_PLATFORM(UWP) if (CopyFileFromAppW(srcFilename.ToWString().c_str(), destFilename.ToWString().c_str(), FALSE)) return true; +#if !defined(__LIBRETRO__) + else { + if (CopyUWP(srcFilename.ToString(), destFilename.ToString())) { + return true; + } + } +#endif #else if (CopyFile(srcFilename.ToWString().c_str(), destFilename.ToWString().c_str(), FALSE)) return true; @@ -780,6 +846,11 @@ bool Move(const Path &srcFilename, const Path &destFilename) { } else if (Copy(srcFilename, destFilename)) { return Delete(srcFilename); } else { +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + if (MoveUWP(srcFilename.ToString(), destFilename.ToString())) { + return true; + } +#endif return false; } } @@ -828,11 +899,15 @@ uint64_t GetFileSize(const Path &filename) { #if defined(_WIN32) && defined(UNICODE) WIN32_FILE_ATTRIBUTE_DATA attr; #if PPSSPP_PLATFORM(UWP) - if (!GetFileAttributesExFromAppW(filename.ToWString().c_str(), GetFileExInfoStandard, &attr)) + if (!GetFileAttributesExFromAppW(filename.ToWString().c_str(), GetFileExInfoStandard, &attr)) { +#if !defined(__LIBRETRO__) + return (uint64_t)GetSizeUWP(filename.ToString()); +#endif #else - if (!GetFileAttributesEx(filename.ToWString().c_str(), GetFileExInfoStandard, &attr)) + if (!GetFileAttributesEx(filename.ToWString().c_str(), GetFileExInfoStandard, &attr)){ #endif return 0; + } if (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) return 0; return ((uint64_t)attr.nFileSizeHigh << 32) | (uint64_t)attr.nFileSizeLow; @@ -945,7 +1020,9 @@ bool OpenFileInEditor(const Path &fileName) { #if PPSSPP_PLATFORM(WINDOWS) #if PPSSPP_PLATFORM(UWP) - // Do nothing. +#if !defined(__LIBRETRO__) + OpenFile(fileName.ToString()); +#endif #else ShellExecuteW(nullptr, L"open", fileName.ToWString().c_str(), nullptr, nullptr, SW_SHOW); #endif diff --git a/Core/FileLoaders/LocalFileLoader.cpp b/Core/FileLoaders/LocalFileLoader.cpp index 3a329cc36d..71f7097c0e 100644 --- a/Core/FileLoaders/LocalFileLoader.cpp +++ b/Core/FileLoaders/LocalFileLoader.cpp @@ -33,6 +33,9 @@ #include "Common/CommonWindows.h" #if PPSSPP_PLATFORM(UWP) #include +#if !defined(__LIBRETRO__) +#include "UWP/UWPHelpers/StorageManager.h" +#endif #endif #else #include @@ -108,7 +111,15 @@ LocalFileLoader::LocalFileLoader(const Path &filename) handle_ = CreateFile(filename.ToWString().c_str(), access, share, nullptr, mode, flags, nullptr); #endif if (handle_ == INVALID_HANDLE_VALUE) { +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + //Use UWP StorageManager to get handle + handle_ = CreateFileUWP(filename_.ToString(), access, share, mode); + if (handle_ == INVALID_HANDLE_VALUE) { + return; + } +#else return; +#endif } LARGE_INTEGER end_offset; const LARGE_INTEGER zero{}; @@ -159,7 +170,11 @@ bool LocalFileLoader::Exists() { if (File::GetFileInfo(filename_, &info)) { return info.exists; } else { +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + return IsExistsUWP(filename_.ToString()); +#else return false; +#endif } } @@ -168,7 +183,11 @@ bool LocalFileLoader::IsDirectory() { if (File::GetFileInfo(filename_, &info)) { return info.exists && info.isDirectory; } +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + return IsDirectoryUWP(filename_.ToString()); +#else return false; +#endif } s64 LocalFileLoader::FileSize() { diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index f6b5659243..c3defa8a81 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -52,6 +52,10 @@ #include #if PPSSPP_PLATFORM(UWP) #include +#if !defined(__LIBRETRO__) +#include "UWP/UWPHelpers/StorageManager.h" +#include "UWP/UWPHelpers/PPSSPPTypesHelpers.h" +#endif #endif #undef FILE_OPEN #else @@ -171,6 +175,13 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File hFile = CreateFile(fullName.ToWString().c_str(), desired, sharemode, 0, openmode, 0, 0); #endif bool success = hFile != INVALID_HANDLE_VALUE; +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + if (!success) { + //Use UWP StorageManager to get handle + hFile = CreateFileUWP(fullName.ToString(), desired, sharemode, openmode); + success = hFile != INVALID_HANDLE_VALUE; + } +#endif if (!success) { DWORD w32err = GetLastError(); @@ -183,6 +194,13 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File hFile = CreateFile(fullName.ToWString().c_str(), desired, sharemode, 0, openmode, 0, 0); #endif success = hFile != INVALID_HANDLE_VALUE; +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + if (!success) { + //Use UWP StorageManager to get handle + hFile = CreateFileUWP(fullName.ToString(), desired, sharemode, openmode); + success = hFile != INVALID_HANDLE_VALUE; + } +#endif if (!success) { w32err = GetLastError(); } diff --git a/Core/FileSystems/VirtualDiscFileSystem.cpp b/Core/FileSystems/VirtualDiscFileSystem.cpp index d141b5a419..4e97dbec89 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.cpp +++ b/Core/FileSystems/VirtualDiscFileSystem.cpp @@ -41,6 +41,10 @@ #include #if PPSSPP_PLATFORM(UWP) #include +#if !defined(__LIBRETRO__) +#include "UWP/UWPHelpers/StorageManager.h" +#include "UWP/UWPHelpers/PPSSPPTypesHelpers.h" +#endif #endif #else #include @@ -684,6 +688,40 @@ std::vector VirtualDiscFileSystem::GetDirListing(const std::string hFind = FindFirstFileEx(w32path.c_str(), FindExInfoStandard, &findData, FindExSearchNameMatch, NULL, 0); #endif if (hFind == INVALID_HANDLE_VALUE) { +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + // Getting folder contents will need extra work + // request must be done within StorageManager + auto contents = GetFolderContents(w32path); + if (!contents.empty()) { + //Copy files data from 'files' to 'myVector' + for each (auto item in contents) { + PSPFileInfo entry; + if (item.isDirectory) { + entry.type = FILETYPE_DIRECTORY; + } + else { + entry.type = FILETYPE_NORMAL; + } + + entry.access = 0555; + entry.exists = true; + entry.size = item.size; + entry.name = item.name; + //tmFromFiletime(entry.atime, file.atime); //find solution if this value is important + //tmFromFiletime(entry.ctime, file.ctime); //find solution if this value is important + //tmFromFiletime(entry.mtime, file.mtime); //find solution if this value is important + entry.isOnSectorSystem = true; + + std::string fullRelativePath = path + "/" + entry.name; + int fileIndex = getFileListIndex(fullRelativePath); + if (fileIndex != -1) + entry.startSector = fileList[fileIndex].firstBlock; + + myVector.push_back(entry); + } + return myVector; + } +#endif if (exists) *exists = false; return myVector; //the empty list diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index c23c29ed43..8e0439b828 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -418,6 +418,11 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { std::string homebrewName = PSP_CoreParameter().fileToStart.ToVisualString(); std::size_t lslash = homebrewName.find_last_of("/"); +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + if (lslash == homebrewName.npos) { + lslash = homebrewName.find_last_of("\\"); + } +#endif if (lslash != homebrewName.npos) homebrewName = homebrewName.substr(lslash + 1); std::string homebrewTitle = g_paramSFO.GetValueString("TITLE"); diff --git a/Core/System.cpp b/Core/System.cpp index fc4d207fd1..471bf936b4 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -767,6 +767,9 @@ void InitSysDirectories() { File::CreateDir(GetSysDirectory(DIRECTORY_SAVEDATA)); File::CreateDir(GetSysDirectory(DIRECTORY_SAVESTATE)); File::CreateDir(GetSysDirectory(DIRECTORY_SYSTEM)); +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + File::CreateDir(GetSysDirectory(DIRECTORY_TEXTURES)); +#endif if (g_Config.currentDirectory.empty()) { g_Config.currentDirectory = GetSysDirectory(DIRECTORY_GAME); diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index 9d5fe26d43..77aa86c2f6 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -48,6 +48,10 @@ #include "Core/Util/GameManager.h" #include "Common/Data/Text/I18n.h" +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) +#include "UWP/UWPHelpers/StorageManager.h" +#endif + GameManager g_GameManager; static struct zip *ZipOpenPath(Path fileName) { @@ -75,6 +79,12 @@ GameManager::GameManager() { Path GameManager::GetTempFilename() const { #ifdef _WIN32 +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + std::string tmpFile = GetTempFile("PSP.tmp"); + if (!tmpFile.empty()) { + return Path(tmpFile); + } +#endif wchar_t tempPath[MAX_PATH]; GetTempPath(MAX_PATH, tempPath); wchar_t buffer[MAX_PATH]; diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index 17cf9165e1..7cbca449c1 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -189,7 +189,12 @@ UI::EventReturn CwCheatScreen::OnEditCheatFile(UI::EventParams ¶ms) { } if (engine_) { #if PPSSPP_PLATFORM(UWP) +#if !defined(__LIBRETRO__) + // UWP storage manager will attempt to launch the file + File::OpenFileInEditor(engine_->CheatFilename()); +#else System_LaunchUrl(LaunchUrlType::BROWSER_URL, engine_->CheatFilename().c_str()); +#endif #else File::OpenFileInEditor(engine_->CheatFilename()); #endif diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 59feb097de..3cd51828ce 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -67,6 +67,9 @@ #include "UI/ControlMappingScreen.h" #include "UI/GameSettingsScreen.h" +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) +#include "UWP/UWPHelpers/StorageManager.h" +#endif #ifdef _WIN32 #include "Common/CommonWindows.h" @@ -591,7 +594,7 @@ void SystemInfoScreen::CreateViews() { #endif deviceSpecs->Add(new ItemHeader(si->T("Display Information"))); -#if PPSSPP_PLATFORM(ANDROID) +#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(UWP) deviceSpecs->Add(new InfoItem(si->T("Native Resolution"), StringFromFormat("%dx%d", System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)))); @@ -653,11 +656,19 @@ void SystemInfoScreen::CreateViews() { storage->Add(new ItemHeader(si->T("Directories"))); // Intentionally non-translated +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + // If location set to local folder the path will be too long to view + // it's better to show simplified text like 'LocalState' using 'GetPreviewPath' + storage->Add(new InfoItem("MemStickDirectory", GetPreviewPath(g_Config.memStickDirectory.ToVisualString()))); + storage->Add(new InfoItem("InternalDataDirectory", GetPreviewPath(g_Config.internalDataDirectory.ToVisualString()))); + storage->Add(new InfoItem("AppCacheDir", GetPreviewPath(g_Config.appCacheDirectory.ToVisualString()))); + storage->Add(new InfoItem("DefaultCurrentDir", GetPreviewPath(g_Config.defaultCurrentDirectory.ToVisualString()))); +#else storage->Add(new InfoItem("MemStickDirectory", g_Config.memStickDirectory.ToVisualString())); storage->Add(new InfoItem("InternalDataDirectory", g_Config.internalDataDirectory.ToVisualString())); storage->Add(new InfoItem("AppCacheDir", g_Config.appCacheDirectory.ToVisualString())); storage->Add(new InfoItem("DefaultCurrentDir", g_Config.defaultCurrentDirectory.ToVisualString())); - +#endif #if PPSSPP_PLATFORM(ANDROID) storage->Add(new InfoItem("ExtFilesDir", g_extFilesDir)); bool scoped = System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index c374777ab3..8579b682b4 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -94,6 +94,10 @@ extern AndroidAudioState *g_audioState; #endif +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) +#include "UWP/UWPHelpers/StorageManager.h" +#endif + GameSettingsScreen::GameSettingsScreen(const Path &gamePath, std::string gameID, bool editThenRestore) : UIDialogScreenWithGameBackground(gamePath), gameID_(gameID), editThenRestore_(editThenRestore) { prevInflightFrames_ = g_Config.iInflightFrames; @@ -988,6 +992,11 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) { } systemSettings->Add(new InfoItem(sy->T("USB"), usbPath))->SetChoiceStyle(true); } +#elif PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + memstickDisplay_ = GetPreviewPath(g_Config.memStickDirectory.ToString()); + auto memstickPath = systemSettings->Add(new ChoiceWithValueDisplay(&memstickDisplay_, sy->T("Memory Stick folder", "Memory Stick folder"), I18NCat::NONE)); + memstickPath->SetEnabled(!PSP_IsInited()); + memstickPath->OnClick.Handle(this, &GameSettingsScreen::OnChangeMemStickDir); #elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) SavePathInMyDocumentChoice = systemSettings->Add(new CheckBox(&installed_, sy->T("Save path in My Documents", "Save path in My Documents"))); SavePathInMyDocumentChoice->SetEnabled(!PSP_IsInited()); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 09c75f64a6..2bdbddb101 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -1167,11 +1167,11 @@ void MainScreen::CreateViews() { } } -#if !PPSSPP_PLATFORM(UWP) - // Having an exit button is against UWP guidelines. +//#if !PPSSPP_PLATFORM(UWP) + // Having an exit button is against UWP guidelines?. rightColumnChoices->Add(new Spacer(25.0)); rightColumnChoices->Add(new Choice(mm->T("Exit")))->OnClick.Handle(this, &MainScreen::OnExit); -#endif +//#endif if (vertical) { root_ = new LinearLayout(ORIENT_VERTICAL); diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index 7e118cfbb6..7942f558a9 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -48,6 +48,10 @@ #include "UI/MainScreen.h" #include "UI/MiscScreens.h" +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) +#include "UWP/UWPHelpers/StorageManager.h" +#endif + static bool FolderSeemsToBeUsed(Path newMemstickFolder) { // Inspect the potential new folder, quickly. if (File::Exists(newMemstickFolder / "PSP/SAVEDATA") || File::Exists(newMemstickFolder / "SAVEDATA")) { @@ -78,12 +82,21 @@ static bool SwitchMemstickFolderTo(Path newMemstickFolder) { } Path memStickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt"; +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + File::Delete(memStickDirFile); + if (newMemstickFolder != Path(GetLocalFolder())) { +#endif + std::string str = newMemstickFolder.ToString(); if (!File::WriteDataToFile(true, str.c_str(), (unsigned int)str.size(), memStickDirFile)) { ERROR_LOG(SYSTEM, "Failed to write memstick path '%s' to '%s'", newMemstickFolder.c_str(), memStickDirFile.c_str()); // Not sure what to do if this file can't be written. Disk full? } +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + } +#endif + // Save so the settings, at least, are transferred. g_Config.memStickDirectory = newMemstickFolder; g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM)); @@ -124,7 +137,11 @@ MemStickScreen::MemStickScreen(bool initialSetup) } } else { // Detect the current choice, so it's preselected in the UI. +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + if (g_Config.memStickDirectory == Path(GetLocalFolder())) { +#else if (g_Config.memStickDirectory == Path(g_extFilesDir)) { +#endif choice_ = CHOICE_PRIVATE_DIRECTORY; } else if (g_Config.memStickDirectory == Path(g_externalDir)) { choice_ = CHOICE_STORAGE_ROOT; @@ -164,16 +181,20 @@ static void AddExplanation(UI::ViewGroup *viewGroup, MemStickScreen::Choice choi case MemStickScreen::CHOICE_BROWSE_FOLDER: holder->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP"), flags, false))->SetBullet(true); holder->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold"), flags, false))->SetBullet(true); +#if !PPSSPP_PLATFORM(UWP) holder->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access"), flags, false))->SetBullet(true); +#endif break; case MemStickScreen::CHOICE_PRIVATE_DIRECTORY: // Consider https://www.compart.com/en/unicode/U+26A0 (unicode warning sign?)? or a graphic? holder->Add(new TextView(iz->T("DataWillBeLostOnUninstall", "Warning! Data will be lost when you uninstall PPSSPP!"), flags, false))->SetBullet(true); holder->Add(new TextView(iz->T("DataCannotBeShared", "Data CANNOT be shared between PPSSPP regular/Gold!"), flags, false))->SetBullet(true); +#if !PPSSPP_PLATFORM(UWP) #if GOLD holder->Add(new TextView(iz->T("USBAccessThroughGold", "USB access through Android/data/org.ppsspp.ppssppgold/files"), flags, false))->SetBullet(true); #else holder->Add(new TextView(iz->T("USBAccessThrough", "USB access through Android/data/org.ppsspp.ppsspp/files"), flags, false))->SetBullet(true); +#endif #endif break; case MemStickScreen::CHOICE_SET_MANUAL: @@ -250,12 +271,19 @@ void MemStickScreen::CreateViews() { if (choice_ == CHOICE_BROWSE_FOLDER || choice_ == CHOICE_SET_MANUAL) { UI::View *extraView = nullptr; if (!g_Config.memStickDirectory.empty()) { +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + extraView = new TextView(StringFromFormat(" %s: %s", iz->T("Current"), GetPreviewPath(g_Config.memStickDirectory.ToString()).c_str()), ALIGN_LEFT, false); +#else extraView = new TextView(StringFromFormat(" %s: %s", iz->T("Current"), g_Config.memStickDirectory.ToVisualString().c_str()), ALIGN_LEFT, false); +#endif } AddExplanation(leftColumn, (MemStickScreen::Choice)choice_, extraView); } - +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + std::string privateString = iz->T("Use App Local Data"); +#else std::string privateString = iz->T("Use App Private Data"); +#endif if (initialSetup_) { privateString = StringFromFormat("%s (%s)", iz->T("Skip for now"), privateString.c_str()); } @@ -277,7 +305,11 @@ void MemStickScreen::CreateViews() { case CHOICE_PRIVATE_DIRECTORY: if (initialSetup_) { confirmButtonText = di->T("Skip"); +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + confirmButtonImage = ImageID("I_ARROW_RIGHT"); +#else confirmButtonImage = ImageID("I_WARNING"); +#endif } else { confirmButtonText = di->T("OK"); } @@ -399,7 +431,11 @@ UI::EventReturn MemStickScreen::SetFolderManually(UI::EventParams ¶ms) { } UI::EventReturn MemStickScreen::UseInternalStorage(UI::EventParams ¶ms) { +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + Path pendingMemStickFolder = Path(GetLocalFolder()); +#else Path pendingMemStickFolder = Path(g_extFilesDir); +#endif if (initialSetup_) { // There's not gonna be any files here in this case since it's a fresh install. @@ -562,7 +598,11 @@ void ConfirmMemstickMoveScreen::CreateViews() { if (!initialSetup_) { leftColumn->Add(new TextView(iz->T("PPSSPP will restart after the change"), ALIGN_LEFT, false)); } +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + leftColumn->Add(new TextView(GetPreviewPath(newMemstickFolder_.ToString()), ALIGN_LEFT, false)); +#else leftColumn->Add(new TextView(newMemstickFolder_.ToVisualString(), ALIGN_LEFT, false)); +#endif std::string newFreeSpaceText = std::string(iz->T("Free space")) + ": " + FormatSpaceString(freeSpaceNew); leftColumn->Add(new TextView(newFreeSpaceText, ALIGN_LEFT, false)); if (existingFilesInNewFolder_) { @@ -577,8 +617,13 @@ void ConfirmMemstickMoveScreen::CreateViews() { if (!oldMemstickFolder.empty()) { std::string oldFreeSpaceText = std::string(iz->T("Free space")) + ": " + FormatSpaceString(freeSpaceOld); + rightColumn->Add(new TextView(std::string(iz->T("Current")) + ":", ALIGN_LEFT, false)); +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + rightColumn->Add(new TextView(GetPreviewPath(oldMemstickFolder.ToString()), ALIGN_LEFT, false)); +#else rightColumn->Add(new TextView(oldMemstickFolder.ToVisualString(), ALIGN_LEFT, false)); +#endif rightColumn->Add(new TextView(oldFreeSpaceText, ALIGN_LEFT, false)); } @@ -666,7 +711,11 @@ UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams ¶ms) { // TODO: Handle failure listing files. std::string error = "Failed to read old directory"; INFO_LOG(SYSTEM, "%s", error.c_str()); +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + progressReporter_.Set(error.c_str()); // it's throwing exception with 'iz->T' +#else progressReporter_.Set(iz->T(error.c_str())); +#endif return new MoveResult{ false, error }; } diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index 3912e095bc..f88a7c5bb1 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -80,7 +80,11 @@ private: bool storageBrowserWorking_; bool done_ = false; +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + int choice_ = CHOICE_PRIVATE_DIRECTORY; +#else int choice_ = 0; +#endif }; class ProgressReporter { @@ -126,7 +130,11 @@ private: Path newMemstickFolder_; bool existingFilesInNewFolder_; +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + bool moveData_ = false; +#else bool moveData_ = true; +#endif bool initialSetup_; ProgressReporter progressReporter_; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index e1f858c2c3..bb43d122c0 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -146,6 +146,10 @@ #include +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) +#include "UWP/UWPHelpers/StorageManager.h" +#endif + ScreenManager *g_screenManager; std::string config_filename; @@ -491,7 +495,34 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch if (!System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { CreateDirectoriesAndroid(); } - +#elif PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + Path memstickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt"; + auto pathResolved = Path(ResolvePathUWP(memstickDirFile.ToString())); + if (File::Exists(pathResolved)) { + INFO_LOG(SYSTEM, "Reading '%s' to find memstick dir.", pathResolved.c_str()); + std::string memstickDir; + if (File::ReadFileToString(true, pathResolved, memstickDir)) { + Path memstickPath(memstickDir); + if (!memstickPath.empty() && File::Exists(memstickPath)) { + g_Config.memStickDirectory = memstickPath; + g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM)); + g_Config.Reload(); + INFO_LOG(SYSTEM, "Memstick Directory from memstick_dir.txt: '%s'", g_Config.memStickDirectory.c_str()); + } + else { + ERROR_LOG(SYSTEM, "Couldn't read directory '%s' specified by memstick_dir.txt.", memstickDir.c_str()); + if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { + // Ask the user to configure a memstick directory. + INFO_LOG(SYSTEM, "Asking the user."); + g_Config.memStickDirectory.clear(); + } + } + } + } + else { + INFO_LOG(SYSTEM, "No memstick directory file found (tried to open '%s')", pathResolved.c_str()); + } + SetWorkingFolder(g_Config.memStickDirectory.ToString()); #elif PPSSPP_PLATFORM(IOS) g_Config.defaultCurrentDirectory = g_Config.internalDataDirectory; g_Config.memStickDirectory = DarwinFileSystemServices::appropriateMemoryStickDirectoryToUse(); @@ -756,7 +787,11 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch DEBUG_LOG(SYSTEM, "ScreenManager!"); g_screenManager = new ScreenManager(); +#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) + if (g_Config.memStickDirectory.empty() || (IsFirstStart() && isLocalState(g_Config.memStickDirectory.ToString()))) { +#else if (g_Config.memStickDirectory.empty()) { +#endif INFO_LOG(SYSTEM, "No memstick directory! Asking for one to be configured."); g_screenManager->switchScreen(new LogoScreen(AfterLogoScreen::MEMSTICK_SCREEN_INITIAL_SETUP)); } else if (gotoGameSettings) { diff --git a/UWP/App.cpp b/UWP/App.cpp index 13611a04f2..11797bab4d 100644 --- a/UWP/App.cpp +++ b/UWP/App.cpp @@ -14,8 +14,12 @@ #include -using namespace UWP; +#include +#include +#include +using namespace UWP; + using namespace concurrency; using namespace Windows::ApplicationModel; using namespace Windows::ApplicationModel::Core; @@ -25,6 +29,7 @@ using namespace Windows::UI::Input; using namespace Windows::System; using namespace Windows::Foundation; using namespace Windows::Graphics::Display; +using namespace Windows::Storage; // The main function is only used to initialize our IFrameworkView class. [Platform::MTAThread] @@ -121,7 +126,8 @@ bool App::HasBackButton() { void App::App_BackRequested(Platform::Object^ sender, Windows::UI::Core::BackRequestedEventArgs^ e) { if (m_isPhone) { e->Handled = m_main->OnHardwareButton(HardwareButton::BACK); - } else { + } + else { e->Handled = true; } } @@ -158,7 +164,7 @@ void App::OnPointerPressed(Windows::UI::Core::CoreWindow^ sender, Windows::UI::C float X = args->CurrentPoint->Position.X; float Y = args->CurrentPoint->Position.Y; int64_t timestamp = args->CurrentPoint->Timestamp; - m_main->OnTouchEvent(TOUCH_DOWN|TOUCH_MOVE, pointerId, X, Y, (double)timestamp); + m_main->OnTouchEvent(TOUCH_DOWN | TOUCH_MOVE, pointerId, X, Y, (double)timestamp); if (!m_isPhone) { sender->SetPointerCapture(); } @@ -171,7 +177,7 @@ void App::OnPointerReleased(Windows::UI::Core::CoreWindow^ sender, Windows::UI:: float X = args->CurrentPoint->Position.X; float Y = args->CurrentPoint->Position.Y; int64_t timestamp = args->CurrentPoint->Timestamp; - m_main->OnTouchEvent(TOUCH_UP|TOUCH_MOVE, pointerId, X, Y, (double)timestamp); + m_main->OnTouchEvent(TOUCH_UP | TOUCH_MOVE, pointerId, X, Y, (double)timestamp); if (!m_isPhone) { sender->ReleasePointerCapture(); } @@ -201,7 +207,8 @@ void App::Run() { if (m_main->Render()) { m_deviceResources->Present(); } - } else { + } + else { CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending); } } @@ -214,7 +221,7 @@ void App::Uninitialize() { } // Application lifecycle event handlers. - +extern LaunchItem launchItem; void App::OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args) { // Run() won't start until the CoreWindow is activated. CoreWindow::GetForCurrentThread()->Activate(); @@ -224,6 +231,17 @@ void App::OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ if (g_Config.UseFullScreen()) Windows::UI::ViewManagement::ApplicationView::GetForCurrentView()->TryEnterFullScreenMode(); + + if (args->Kind == ActivationKind::File) { + FileActivatedEventArgs^ protocolArgs = dynamic_cast(args); + launchItem = LaunchItem((StorageFile^)protocolArgs->Files->GetAt(0)); + } + if (args->Kind == ActivationKind::Protocol) + { + unsigned i; + ProtocolActivatedEventArgs^ protocolArgs = dynamic_cast(args); + launchItem = LaunchItem(protocolArgs); + } } void App::OnSuspending(Platform::Object^ sender, SuspendingEventArgs^ args) { @@ -235,9 +253,10 @@ void App::OnSuspending(Platform::Object^ sender, SuspendingEventArgs^ args) { auto app = this; create_task([app, deferral]() { + g_Config.Save("App::OnSuspending"); app->m_deviceResources->Trim(); - deferral->Complete(); - }); + deferral->Complete(); + }); } void App::OnResuming(Platform::Object^ sender, Platform::Object^ args) { @@ -277,7 +296,8 @@ void App::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ ar // MainScreen::OnExit and even App::OnWindowClosed // doesn't seem to be called when closing the window // Try to save the config here - g_Config.Save("App::OnVisibilityChanged"); + // OnSuspending should now do the job + //g_Config.Save("App::OnVisibilityChanged"); } m_windowVisible = args->Visible; } diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 904906fbb9..8a69ec3adc 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -37,6 +37,12 @@ #include "UWPUtil.h" #include "App.h" +// UWP Storage helper includes +#include "UWPHelpers/StorageManager.h" +#include "UWPHelpers/StorageAsync.h" +#include + + using namespace UWP; using namespace Windows::Foundation; using namespace Windows::Storage; @@ -52,7 +58,9 @@ extern WindowsAudioBackend *winAudioBackend; std::string langRegion; std::list> g_input; - +// This item will be assigned by 'App.cpp' at 'App::OnActivated' +LaunchItem launchItem; + // TODO: Use Microsoft::WRL::ComPtr<> for D3D11 objects? // TODO: See https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/WindowsAudioSession for WASAPI with UWP // TODO: Low latency input: https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/LowLatencyInput/cpp @@ -111,19 +119,26 @@ PPSSPP_UWPMain::PPSSPP_UWPMain(App ^app, const std::shared_ptrSetAllLogLevels(LogTypes::LDEBUG); } const char *argv[2] = { "fake", nullptr }; - std::string cacheFolder = ConvertWStringToUTF8(ApplicationData::Current->LocalFolder->Path->Data()); + std::string cacheFolder = ConvertWStringToUTF8(ApplicationData::Current->TemporaryFolder->Path->Data()); NativeInit(1, argv, "", "", cacheFolder.c_str()); + // Set log file location + if (g_Config.bEnableLogging) { + // Do cleanup first + CleanupLogs(); + LogManager::GetInstance()->ChangeFileLog(GetLogFile().c_str()); + } + NativeInitGraphics(ctx_.get()); NativeResized(); @@ -161,12 +176,24 @@ void PPSSPP_UWPMain::CreateWindowSizeDependentResources() { ctx_->GetDrawContext()->HandleEvent(Draw::Event::GOT_BACKBUFFER, width, height, m_deviceResources->GetBackBufferRenderTargetView()); } +void PPSSPP_UWPMain::BootToLaunchFile() { + if (launchItem.IsValid() && !launchItem.IsHandled()) { + launchItem.SetState(true); + std::string path = launchItem.GetFilePath(); + if (!path.empty()) { + NativeMessageReceived("boot", path.c_str()); + } + } +} // Renders the current frame according to the current application state. // Returns true if the frame was rendered and is ready to be displayed. bool PPSSPP_UWPMain::Render() { ctx_->GetDrawContext()->HandleEvent(Draw::Event::PRESENTED, 0, 0, nullptr, nullptr); NativeUpdate(); + // This is not perfect but it will do the job + BootToLaunchFile(); + static bool hasSetThreadName = false; if (!hasSetThreadName) { SetCurrentThreadName("UWPRenderThread"); @@ -367,12 +394,15 @@ std::vector System_GetPropertyStringVec(SystemProperty prop) { tempPath.resize(sz); result.push_back(ConvertWStringToUTF8(tempPath)); - if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0) + // As per below 'getenv' isn't supported + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getenv-wgetenv?view=msvc-170 + /*if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0) result.push_back(getenv("TMPDIR")); if (getenv("TMP") && strlen(getenv("TMP")) != 0) result.push_back(getenv("TMP")); if (getenv("TEMP") && strlen(getenv("TEMP")) != 0) - result.push_back(getenv("TEMP")); + result.push_back(getenv("TEMP"));*/ + return result; } @@ -385,6 +415,7 @@ int System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_AUDIO_SAMPLE_RATE: return winAudioBackend ? winAudioBackend->GetSampleRate() : -1; + case SYSPROP_DEVICE_TYPE: { auto ver = Windows::System::Profile::AnalyticsInfo::VersionInfo; @@ -396,6 +427,16 @@ int System_GetPropertyInt(SystemProperty prop) { return DEVICE_TYPE_DESKTOP; } } + case SYSPROP_DISPLAY_XRES: + { + CoreWindow^ corewindow = CoreWindow::GetForCurrentThread(); + return (int)corewindow->Bounds.Width; + } + case SYSPROP_DISPLAY_YRES: + { + CoreWindow^ corewindow = CoreWindow::GetForCurrentThread(); + return (int)corewindow->Bounds.Height; + } default: return -1; } @@ -420,11 +461,11 @@ void System_Toast(const char *str) {} bool System_GetPropertyBool(SystemProperty prop) { switch (prop) { case SYSPROP_HAS_OPEN_DIRECTORY: - return false; + return true; case SYSPROP_HAS_FILE_BROWSER: return true; case SYSPROP_HAS_FOLDER_BROWSER: - return false; // at least I don't know a usable one + return true; // now supported case SYSPROP_HAS_IMAGE_BROWSER: return true; // we just use the file browser case SYSPROP_HAS_BACK_BUTTON: @@ -462,48 +503,95 @@ void System_Notify(SystemNotification notification) { bool System_MakeRequest(SystemRequestType type, int requestId, const std::string ¶m1, const std::string ¶m2, int param3) { switch (type) { + + case SystemRequestType::EXIT_APP: + { + bool state = false; + ExecuteTask(state, Windows::UI::ViewManagement::ApplicationView::GetForCurrentView()->TryConsolidateAsync()); + if (!state) { + // Notify the user? + } + return true; + } + case SystemRequestType::RESTART_APP: + { + Windows::ApplicationModel::Core::AppRestartFailureReason error; + ExecuteTask(error, Windows::ApplicationModel::Core::CoreApplication::RequestRestartAsync(nullptr)); + if (error != Windows::ApplicationModel::Core::AppRestartFailureReason::RestartPending) { + // Shutdown + System_MakeRequest(SystemRequestType::EXIT_APP, requestId, param1, param2, param3); + } + return true; + } case SystemRequestType::BROWSE_FOR_IMAGE: + { + std::vector supportedExtensions = { ".jpg", ".png" }; + + //Call file picker + ChooseFile(supportedExtensions).then([requestId](std::string filePath) { + if (filePath.size() > 1) { + g_requestManager.PostSystemSuccess(requestId, filePath.c_str()); + } + else { + g_requestManager.PostSystemFailure(requestId); + } + }); + return true; + } case SystemRequestType::BROWSE_FOR_FILE: { - auto picker = ref new Windows::Storage::Pickers::FileOpenPicker(); - picker->ViewMode = Pickers::PickerViewMode::List; - - if (type == SystemRequestType::BROWSE_FOR_IMAGE) { - picker->FileTypeFilter->Append(".jpg"); - picker->FileTypeFilter->Append(".png"); - } else { - switch ((BrowseFileType)param3) { - case BrowseFileType::BOOTABLE: - // These are single files that can be loaded directly using StorageFileLoader. - picker->FileTypeFilter->Append(".cso"); - picker->FileTypeFilter->Append(".iso"); - - // Can't load these this way currently, they require mounting the underlying folder. - picker->FileTypeFilter->Append(".bin"); - picker->FileTypeFilter->Append(".elf"); - break; - case BrowseFileType::INI: - picker->FileTypeFilter->Append(".ini"); - break; - case BrowseFileType::DB: - picker->FileTypeFilter->Append(".db"); - break; - case BrowseFileType::ANY: - picker->FileTypeFilter->Append("*"); - break; - } + std::vector supportedExtensions = { ".cso", ".bin", ".iso", ".elf", ".zip" }; + switch ((BrowseFileType)param3) { + case BrowseFileType::INI: + supportedExtensions = { ".ini" }; + break; + case BrowseFileType::DB: + supportedExtensions = { ".db" }; + break; + case BrowseFileType::ANY: + supportedExtensions = {}; + break; } - picker->SuggestedStartLocation = Pickers::PickerLocationId::DocumentsLibrary; - - create_task(picker->PickSingleFileAsync()).then([requestId](StorageFile ^file) { - if (file) { - std::string path = FromPlatformString(file->Path); - g_requestManager.PostSystemSuccess(requestId, path.c_str()); - } else { + //Call file picker + ChooseFile(supportedExtensions).then([requestId](std::string filePath) { + if (filePath.size() > 1) { + g_requestManager.PostSystemSuccess(requestId, filePath.c_str()); + } + else { g_requestManager.PostSystemFailure(requestId); } }); + + return true; + } + case SystemRequestType::BROWSE_FOR_FOLDER: + { + ChooseFolder().then([requestId](std::string folderPath) { + if (folderPath.size() > 1) { + g_requestManager.PostSystemSuccess(requestId, folderPath.c_str()); + } + else { + g_requestManager.PostSystemFailure(requestId); + } + }); + return true; + } + case SystemRequestType::NOTIFY_UI_STATE: + { + if (!param1.empty() && !strcmp(param1.c_str(), "menu")) { + if (launchItem.IsValid() && launchItem.IsHandled()) { + launchItem.Close(); + } + } + return true; + } + case SystemRequestType::COPY_TO_CLIPBOARD: + { + auto dataPackage = ref new DataPackage(); + dataPackage->RequestedOperation = DataPackageOperation::Copy; + dataPackage->SetText(convert(param1)); + Clipboard::SetContent(dataPackage); return true; } case SystemRequestType::TOGGLE_FULLSCREEN_STATE: @@ -528,7 +616,13 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string } void System_ShowFileInFolder(const char *path) { - // Unsupported + auto ver = Windows::System::Profile::AnalyticsInfo::VersionInfo; + if (ver->DeviceFamily == "Windows.Xbox") { + // Unsupported + } + else { + OpenFolder(std::string(path)); + } } void System_LaunchUrl(LaunchUrlType urlType, const char *url) { diff --git a/UWP/PPSSPP_UWPMain.h b/UWP/PPSSPP_UWPMain.h index 3be7b25ce4..939de3fed4 100644 --- a/UWP/PPSSPP_UWPMain.h +++ b/UWP/PPSSPP_UWPMain.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include @@ -36,6 +36,7 @@ public: PPSSPP_UWPMain(App ^app, const std::shared_ptr& deviceResources); ~PPSSPP_UWPMain(); void CreateWindowSizeDependentResources(); + void BootToLaunchFile(); bool Render(); // IDeviceNotify @@ -70,4 +71,4 @@ private: std::unique_ptr ctx_; }; -} \ No newline at end of file +} diff --git a/UWP/Package.appxmanifest b/UWP/Package.appxmanifest index 967c48e26c..947d21d2e3 100644 --- a/UWP/Package.appxmanifest +++ b/UWP/Package.appxmanifest @@ -23,6 +23,21 @@ + + + + + .cso + .bin + .iso + .elf + + + + + + + @@ -32,5 +47,7 @@ + + diff --git a/UWP/PackageGold.appxmanifest b/UWP/PackageGold.appxmanifest index 384fa2d9d7..034b3fa83a 100644 --- a/UWP/PackageGold.appxmanifest +++ b/UWP/PackageGold.appxmanifest @@ -27,6 +27,21 @@ + + + + + .cso + .bin + .iso + .elf + + + + + + + @@ -36,5 +51,7 @@ + + diff --git a/UWP/PackageNormal.appxmanifest b/UWP/PackageNormal.appxmanifest index cc6f628a24..d6dbb88220 100644 --- a/UWP/PackageNormal.appxmanifest +++ b/UWP/PackageNormal.appxmanifest @@ -23,6 +23,21 @@ + + + + + .cso + .bin + .iso + .elf + + + + + + + @@ -32,5 +47,7 @@ + + diff --git a/UWP/UWP.vcxproj b/UWP/UWP.vcxproj index 4e7b0db3b8..94cdf9344e 100644 --- a/UWP/UWP.vcxproj +++ b/UWP/UWP.vcxproj @@ -458,6 +458,21 @@ + + + + + + + + + + + + + + + @@ -469,6 +484,16 @@ + + + + + + + + + + Create @@ -953,4 +978,4 @@ - + \ No newline at end of file diff --git a/UWP/UWP.vcxproj.filters b/UWP/UWP.vcxproj.filters index 5b018c1498..1a889435c7 100644 --- a/UWP/UWP.vcxproj.filters +++ b/UWP/UWP.vcxproj.filters @@ -4,10 +4,91 @@ 0d070030-beb5-4f48-99a0-b1777297a468 + + 8d1bdedf-c546-4b7a-8aa7-e490c4e8ad60 + + + 68b36d2a-4f4a-48a5-8b8f-cb15af108c93 + + + 5964c8ad-590e-4b46-84f2-067602f4f231 + a5564d79-b131-4b27-9f5f-d80671731d94 bmp;fbx;gif;jpg;jpeg;tga;tiff;tif;png + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + + + UWPHelpers + Common @@ -76,22 +157,39 @@ - + + Audio + - - + + Input + + + Input + + + UWPHelpers + - + + Audio + - - + + Input + + + Input + - + + Input + @@ -548,4 +646,4 @@ Content - + \ No newline at end of file diff --git a/UWP/UWPHelpers/LaunchItem.h b/UWP/UWPHelpers/LaunchItem.h new file mode 100644 index 0000000000..df161efe07 --- /dev/null +++ b/UWP/UWPHelpers/LaunchItem.h @@ -0,0 +1,115 @@ +// UWP STORAGE MANAGER +// GitHub: https://github.com/basharast/UWP2Win32 + +#pragma once + +#include "pch.h" +#include +#include + +#include "Common/Log.h" +#include "StorageExtensions.h" +#include + + +using namespace Platform; +using namespace Windows::Storage; +using namespace Windows::Foundation; +using namespace Windows::ApplicationModel::Activation; +extern void AddItemToFutureList(IStorageItem^ item); + +class LaunchItem { +public: + LaunchItem() { + } + + LaunchItem(IStorageFile^ file) { + storageFile = file; + AddItemToFutureList(storageFile); + launchPath = std::string(); + launchOnExit = std::string(); + } + + LaunchItem(ProtocolActivatedEventArgs^ args) { + try { + unsigned i; + Windows::Foundation::WwwFormUrlDecoder^ query = args->Uri->QueryParsed; + + for (i = 0; i < query->Size; i++) + { + IWwwFormUrlDecoderEntry^ arg = query->GetAt(i); + + if (arg->Name == "cmd") + { + auto command = convert(arg->Value); + DEBUG_LOG(FILESYS, "Launch command %s", command.c_str()); + + std::regex rgx("\"(.+\[^\/]+)\""); + std::smatch match; + + if (std::regex_search(command, match, rgx)) { + try + { + launchPath = match[1]; + } + catch (...) { + launchPath = match[0]; + } + DEBUG_LOG(FILESYS, "Launch target %s", launchPath.c_str()); + } + } + else if (arg->Name == "launchOnExit") { + launchOnExit = convert(arg->Value); + DEBUG_LOG(FILESYS, "On exit URI %s", launchOnExit.c_str()); + } + } + } + catch (...) { + + } + storageFile = nullptr; + } + + ~LaunchItem() { + delete storageFile; + } + + + bool IsHandled() { + return handled; + } + void SetState(bool fileHandled) { + handled = fileHandled; + } + + bool IsValid() { + return storageFile != nullptr || !launchPath.empty(); + } + + std::string GetFilePath() { + std::string path = launchPath; + if (storageFile != nullptr) { + path = convert(storageFile->Path); + } + return path; + } + + void Close() { + storageFile = nullptr; + launchPath = std::string(); + handled = false; + + if (!launchOnExit.empty()) { + DEBUG_LOG(FILESYS, "Calling back %s", launchOnExit.c_str()); + auto uri = ref new Windows::Foundation::Uri(convert(launchOnExit)); + Windows::System::Launcher::LaunchUriAsync(uri); + } + launchOnExit = std::string(); + } + +private: + IStorageFile^ storageFile; + std::string launchPath; + std::string launchOnExit; + bool handled = false; +}; diff --git a/UWP/UWPHelpers/PPSSPPTypesHelpers.cpp b/UWP/UWPHelpers/PPSSPPTypesHelpers.cpp new file mode 100644 index 0000000000..8230f5ebae --- /dev/null +++ b/UWP/UWPHelpers/PPSSPPTypesHelpers.cpp @@ -0,0 +1,85 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +#include "PPSSPPTypesHelpers.h" +#include "StorageManager.h" +#include "Common/Log.h" + +bool ItemsInfoUWPToFilesInfo(std::list items, std::vector* files, const char* filter, std::set filters) { + bool state = false; + if (!items.empty()) { + for each (auto item in items) { + File::FileInfo info; + info.name = item.name; + info.fullName = Path(item.fullName); + info.exists = true; + info.size = item.size; + info.isDirectory = item.isDirectory; + info.isWritable = (item.attributes & FILE_ATTRIBUTE_READONLY) == 0; + info.atime = item.lastAccessTime; + info.mtime = item.lastWriteTime; + info.ctime = item.creationTime; + if (item.attributes & FILE_ATTRIBUTE_READONLY) { + info.access = 0444; // Read + } + else { + info.access = 0666; // Read/Write + } + if (item.attributes & FILE_ATTRIBUTE_DIRECTORY) { + info.access |= 0111; // Execute + } + 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); + } + state = true; + } + + return state; +} + +void UpdateFileInfoByItemInfo(ItemInfoUWP item, File::FileInfo* info) { + info->name = item.name; + info->fullName = Path(item.fullName); + info->exists = true; + info->size = item.size; + info->isDirectory = item.isDirectory; + info->isWritable = (item.attributes & FILE_ATTRIBUTE_READONLY) == 0; + info->atime = item.lastAccessTime; + info->mtime = item.lastWriteTime; + info->ctime = item.creationTime; + if (item.attributes & FILE_ATTRIBUTE_READONLY) { + info->access = 0444; // Read + } + else { + info->access = 0666; // Read/Write + } + if (item.attributes & FILE_ATTRIBUTE_DIRECTORY) { + info->access |= 0111; // Execute + } +} + +bool GetFileInfoUWP(std::string path, File::FileInfo* info) { + bool state = false; + VERBOSE_LOG(FILESYS, "GetFileInfoUWP (%s)", path.c_str()); + auto itemInfo = GetItemInfoUWP(path); + if (itemInfo.attributes != INVALID_FILE_ATTRIBUTES) { + UpdateFileInfoByItemInfo(itemInfo, info); + state = true; + } + else { + VERBOSE_LOG(FILESYS, "GetFileInfoUWP failed! (%s)", path.c_str()); + } + + return state; +} diff --git a/UWP/UWPHelpers/PPSSPPTypesHelpers.h b/UWP/UWPHelpers/PPSSPPTypesHelpers.h new file mode 100644 index 0000000000..4ad619da7c --- /dev/null +++ b/UWP/UWPHelpers/PPSSPPTypesHelpers.h @@ -0,0 +1,19 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +#pragma once + +#include +#include +#include +#include + +#include "StorageInfo.h" +#include "Common/File/Path.h" +#include "Common/File/DirListing.h" + +bool ItemsInfoUWPToFilesInfo(std::list items,std::vector* files, const char* filter, std::set filters); +bool GetFileInfoUWP(std::string path, File::FileInfo* info); diff --git a/UWP/UWPHelpers/StorageAccess.cpp b/UWP/UWPHelpers/StorageAccess.cpp new file mode 100644 index 0000000000..44c0c8f209 --- /dev/null +++ b/UWP/UWPHelpers/StorageAccess.cpp @@ -0,0 +1,223 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +// Functions: +// GetDataFromLocalSettings(Platform::String^ key) +// AddDataToLocalSettings(Platform::String^ key, Platform::String^ data, bool replace) +// +// AddFolderToFutureList(StorageFolder^ folder) +// AddFileToFutureList(StorageFile^ file) +// +// AddToAccessibleDirectories(StorageFolder^ folder) +// AddToAccessibleFiles(StorageFile^ file) +// UpdateDirectoriesByFutureList() +// UpdateFilesByFutureList() +// FillAccessLists() +// +// GetFolderByKey(Platform::String^ key) +// GetFileByKey(Platform::String^ key) +// AppendFolderByToken(Platform::String^ token) +// AppendFileByToken(Platform::String^ token) + +#include "StorageConfig.h" +#include "StorageLog.h" +#include "StorageExtensions.h" +#include "StorageAsync.h" +#include "StorageAccess.h" +#include "StorageItemW.h" + +using namespace Platform; +using namespace Windows::Storage; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::Storage::AccessCache; +using namespace Windows::ApplicationModel; + +// Main lookup list +std::list FutureAccessItems; + +// Get value from app local settings +Platform::String^ GetDataFromLocalSettings(Platform::String^ key) { + ApplicationDataContainer^ localSettings{ ApplicationData::Current->LocalSettings }; + IPropertySet^ values{ localSettings->Values }; + if (key != nullptr) { + Platform::Object^ tokenRetrive = values->Lookup(key); + if (tokenRetrive != nullptr) { + Platform::String^ ConvertedToken = (Platform::String^)tokenRetrive; + return ConvertedToken; + } + } + return nullptr; +} + +std::string GetDataFromLocalSettings(std::string key) { + return convert(GetDataFromLocalSettings(convert(key))); +} + +// Add or replace value in app local settings +bool AddDataToLocalSettings(Platform::String^ key, Platform::String^ data, bool replace) { + ApplicationDataContainer^ localSettings{ ApplicationData::Current->LocalSettings }; + IPropertySet^ values{ localSettings->Values }; + + Platform::String^ testResult = GetDataFromLocalSettings(key); + if (testResult == nullptr) { + values->Insert(key, PropertyValue::CreateString(data)); + return true; + } + else if (replace) { + values->Remove(key); + values->Insert(key, PropertyValue::CreateString(data)); + return true; + } + + return false; +} + +bool AddDataToLocalSettings(std::string key, std::string data, bool replace) { + return AddDataToLocalSettings(convert(key), convert(data),replace); +} + +// Add item to history list (FutureAccessItems) +void AddToAccessibleItems(IStorageItem^ item) { + bool isFolderAddedBefore = false; + for each (auto folderItem in FutureAccessItems) { + if (folderItem.Equal(item)) { + isFolderAddedBefore = true; + break; + } + } + + if (!isFolderAddedBefore) { + FutureAccessItems.push_back(StorageItemW(item)); + } +} + + +// Add folder to future list (to avoid request picker again) +void AddItemToFutureList(IStorageItem^ item) { + try { + if (item != nullptr) { + Platform::String^ folderToken = AccessCache::StorageApplicationPermissions::FutureAccessList->Add(item); + AddToAccessibleItems(item); + } + } + catch (Platform::COMException^ e) { + } +} + +// Get item by key +// This function can be used when you store token in LocalSettings as custom key +IStorageItem^ GetItemByKey(Platform::String^ key) { + IStorageItem^ item; + Platform::String^ itemToken = GetDataFromLocalSettings(key); + if (itemToken != nullptr && AccessCache::StorageApplicationPermissions::FutureAccessList->ContainsItem(itemToken)) { + ExecuteTask(item, AccessCache::StorageApplicationPermissions::FutureAccessList->GetItemAsync(itemToken)); + } + + return item; +} + +// Append folder by token to (FutureAccessFolders) +void AppendItemByToken(Platform::String^ token) { + try { + if (token != nullptr && AccessCache::StorageApplicationPermissions::FutureAccessList->ContainsItem(token)) { + IStorageItem^ storageItem; + ExecuteTask(storageItem, AccessCache::StorageApplicationPermissions::FutureAccessList->GetItemAsync(token)); + AddToAccessibleItems(storageItem); + } + } + catch (Platform::COMException^ e) { + } +} + +// Update the history list by the future list (to restore all the picked items) +void UpdateItemsByFutureList() { + auto AccessList = AccessCache::StorageApplicationPermissions::FutureAccessList->Entries; + for each (auto ListItem in AccessList) { + Platform::String^ itemToken = ListItem.Token; + AppendItemByToken(itemToken); + } +} + +bool fillListsCalled = false; +bool fillListInProgress = false; +void FillLookupList() { + if (fillListsCalled) { + // Should be called only once + // but let's wait in case we got too calls at once + CoreWindow^ corewindow = CoreWindow::GetForCurrentThread(); + while (fillListInProgress) + { + try { + if (corewindow) { + corewindow->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); + } + else { + corewindow = CoreWindow::GetForCurrentThread(); + } + } + catch (...) { + + } + } + return; + } + fillListsCalled = true; + fillListInProgress = true; + // Clean access list from any deleted/moved items + for each (auto listItem in AccessCache::StorageApplicationPermissions::FutureAccessList->Entries) { + try { + IStorageItem^ test; + ExecuteTask(test, AccessCache::StorageApplicationPermissions::FutureAccessList->GetItemAsync(listItem.Token)); + if (test == nullptr) { + // Access denied or file moved/deleted + AccessCache::StorageApplicationPermissions::FutureAccessList->Remove(listItem.Token); + } + } + catch (Platform::COMException^ e) { + // Access denied or file moved/deleted + AccessCache::StorageApplicationPermissions::FutureAccessList->Remove(listItem.Token); + } + } + + // Get files/folders selected by the user + UpdateItemsByFutureList(); + + // Append known folders +#if APPEND_APP_LOCALDATA_LOCATION + AddToAccessibleItems(ApplicationData::Current->LocalFolder); + AddToAccessibleItems(ApplicationData::Current->TemporaryFolder); +#endif + +#if APPEND_APP_INSTALLATION_LOCATION + AddToAccessibleItems(Package::Current->InstalledLocation); +#endif + +#if APPEND_DOCUMENTS_LOCATION + // >>>>DOCUMENTS (requires 'documentsLibrary' capability) + AddToAccessibleItems(KnownFolders::DocumentsLibrary); +#endif + +#if APPEND_VIDEOS_LOCATION + // >>>>VIDEOS (requires 'videosLibrary' capability) + AddToAccessibleItems(KnownFolders::VideosLibrary); +#endif + +#if APPEND_PICTURES_LOCATION + // >>>>VIDEOS (requires 'picturesLibrary' capability) + AddToAccessibleItems(KnownFolders::PicturesLibrary); +#endif + +#if APPEND_MUSIC_LOCATION + // >>>>MUSIC (requires 'musicLibrary' capability) + AddToAccessibleItems(KnownFolders::MusicLibrary); +#endif + + // No need to append `RemovableDevices` + // they will be allowed for access once you added the capability + + fillListInProgress = false; +} diff --git a/UWP/UWPHelpers/StorageAccess.h b/UWP/UWPHelpers/StorageAccess.h new file mode 100644 index 0000000000..5eddc94afb --- /dev/null +++ b/UWP/UWPHelpers/StorageAccess.h @@ -0,0 +1,22 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +// Functions: +// GetDataFromLocalSettings(std::string key) +// AddDataToLocalSettings(std::string key, std::string data, bool replace) +// +// FillLookupList() + +#pragma once + +#include + +// Local settings +std::string GetDataFromLocalSettings(std::string key); +bool AddDataToLocalSettings(std::string key, std::string data, bool replace); + +// Lookup list +void FillLookupList(); diff --git a/UWP/UWPHelpers/StorageAsync.cpp b/UWP/UWPHelpers/StorageAsync.cpp new file mode 100644 index 0000000000..543c8ff05f --- /dev/null +++ b/UWP/UWPHelpers/StorageAsync.cpp @@ -0,0 +1,32 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +// Thanks to RetroArch/Libretro team for this idea +// This is improved version of the original idea + +// Functions: +// ExecuteTask(out, task) [for IAsyncOperation] +// ExecuteTask(out, task, def) [for IAsyncOperation] +// ExecuteTask(action) [for IAsyncAction such as 'Delete'] + +#include "StorageAsync.h" + +bool ActionPass(Windows::Foundation::IAsyncAction^ action) +{ + return TaskHandler([&]() { + return concurrency::create_task(action).then([]() { + return true; + }); + }, false); +} + +// Async action such as 'Delete' file +// @action: async action +// return false when action failed +bool ExecuteTask(Windows::Foundation::IAsyncAction^ action) +{ + return ActionPass(action); +}; diff --git a/UWP/UWPHelpers/StorageAsync.h b/UWP/UWPHelpers/StorageAsync.h new file mode 100644 index 0000000000..1ef9b5bba5 --- /dev/null +++ b/UWP/UWPHelpers/StorageAsync.h @@ -0,0 +1,106 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +// Thanks to RetroArch/Libretro team for this idea +// This is improved version of the original idea + +// Functions: +// ExecuteTask(out, task) [for IAsyncOperation] +// ExecuteTask(out, task, def) [for IAsyncOperation] +// ExecuteTask(action) [for IAsyncAction such as 'Delete'] + +#pragma once + +#include "pch.h" +#include +#include +#include +#include + +#include "StorageLog.h" +#include "StorageExtensions.h" + +using namespace Windows::UI::Core; + +// Don't add 'using' 'Windows::Foundation' +// it might cause confilct with some types like 'Point' + +#pragma region Async Handlers + +template +T TaskHandler(std::function()> wtask, T def) +{ + T result = def; + bool done = false; + wtask().then([&](concurrency::task t) { + try + { + result = t.get(); + } + catch (Platform::Exception^ exception_) + { + UWP_ERROR_LOG(UWPSMT, convertToChar(exception_->Message)); + } + done = true; + }); + + CoreWindow^ corewindow = CoreWindow::GetForCurrentThread(); + while (!done) + { + try { + if (corewindow) { + corewindow->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); + } + else { + corewindow = CoreWindow::GetForCurrentThread(); + } + } + catch (...) { + + } + } + + return result; +}; + +template +T TaskPass(Windows::Foundation::IAsyncOperation^ task, T def) +{ + return TaskHandler([&]() { + return concurrency::create_task(task).then([](T res) { + return res; + }); + }, def); +} + +bool ActionPass(Windows::Foundation::IAsyncAction^ action); + +#pragma endregion + +// Now it's more simple to execute async task +// @out: output variable +// @task: async task +template +void ExecuteTask(T& out, Windows::Foundation::IAsyncOperation^ task) +{ + out = TaskPass(task, T()); +}; + +// For specific return default value +// @out: output variable +// @task: async task +// @def: default value when fail +template +void ExecuteTask(T& out, Windows::Foundation::IAsyncOperation^ task, T def) +{ + out = TaskPass(task, def); +}; + + +// Async action such as 'Delete' file +// @action: async action +// return false when action failed +bool ExecuteTask(Windows::Foundation::IAsyncAction^ action); diff --git a/UWP/UWPHelpers/StorageConfig.h b/UWP/UWPHelpers/StorageConfig.h new file mode 100644 index 0000000000..89f3b3dbea --- /dev/null +++ b/UWP/UWPHelpers/StorageConfig.h @@ -0,0 +1,23 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +#pragma once + +#include + +// Known locations +// ACTIVATE BELOW ONLY IF YOU ADDED THE CAPABILITY +#define APPEND_APP_LOCALDATA_LOCATION 1 // recommended to be always 1 +#define APPEND_APP_INSTALLATION_LOCATION 1 // recommended to be always 1 +#define APPEND_DOCUMENTS_LOCATION 0 // (requires 'documentsLibrary' capability) +#define APPEND_VIDEOS_LOCATION 0 // (requires 'videosLibrary' capability) +#define APPEND_MUSIC_LOCATION 0 // (requires musicLibrary' capability) +#define APPEND_PICTURES_LOCATION 0 // (requires 'picturesLibrary' capability) + + +// Working folder +// set this value by calling `SetWorkingFolder` from `StorageManager.h` +static std::string AppWorkingFolder; diff --git a/UWP/UWPHelpers/StorageExtensions.cpp b/UWP/UWPHelpers/StorageExtensions.cpp new file mode 100644 index 0000000000..482be1ae3d --- /dev/null +++ b/UWP/UWPHelpers/StorageExtensions.cpp @@ -0,0 +1,316 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +// Functions: +// replace(std::string& str, const std::string& from, const std::string& to) +// replace2(const std::string str, const std::string& from, const std::string& to) +// split(const std::string s, char seperator) +// +// isChild(std::string parent, std::string child) +// isParent(std::string parent, std::string child, std::string childName) +// +// iequals(const std::string a, const std::string b) case-insenstive +// equals(const std::string a, const std::string b) case-senstive +// ends_with(std::string const& value, std::string const& ending) +// starts_with(std::string str, std::string prefix) +// +// convert(const std::string input) +// convertToWString(const std::string input) +// convert(Platform::String^ input) +// convert(std::wstring input) +// convert(const char* input) +// convertToLPCWSTR(Platform::String^ input) +// convertToLPCWSTR(std::string input) +// convertToChar(Platform::String^ input) +// +// tolower(std::string& input) +// tolower(Platform::String^ &input) +// toupper(std::string& input) +// toupper(Platform::String^& input) +// +// windowsPath(std::string& path) +// windowsPath(Platform::String^ &path) +// merge(std::string targetFullPath, std::string subFullPath) +// +// findInList(std::list& inputList, T& str) +// isWriteMode(const char* mode) +// getSubRoot(std::string parent, std::string child) + +#include "StorageExtensions.h" + +#pragma region Inernal Helpers +std::string make_string(const std::wstring& wstring) +{ + if (wstring.empty()) + { + return std::string(); + } + size_t pos; + size_t begin = 0; + std::string ret; + size_t size; + pos = wstring.find(static_cast(0), begin); + while (pos != std::wstring::npos && begin < wstring.length()) + { + std::wstring segment = std::wstring(&wstring[begin], pos - begin); + size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &segment[0], (int)segment.size(), NULL, 0, NULL, NULL); + std::string converted = std::string(size, 0); + WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &segment[0], (int)segment.size(), &converted[0], (int)converted.size(), NULL, NULL); + ret.append(converted); + ret.append({ 0 }); + begin = pos + 1; + pos = wstring.find(static_cast(0), begin); + } + if (begin <= wstring.length()) + { + std::wstring segment = std::wstring(&wstring[begin], wstring.length() - begin); + size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &segment[0], (int)segment.size(), NULL, 0, NULL, NULL); + std::string converted = std::string(size, 0); + WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, &segment[0], (int)segment.size(), &converted[0], (int)converted.size(), NULL, NULL); + ret.append(converted); + } + + return ret; +} + +std::wstring make_wstring(const std::string& string) +{ + size_t requiredSize = 0; + std::wstring answer; + wchar_t* pWTempString = NULL; + + requiredSize = mbstowcs(NULL, string.c_str(), 0) + 1; + + pWTempString = (wchar_t*)malloc(requiredSize * sizeof(wchar_t)); + if (pWTempString != NULL) + { + size_t size = mbstowcs(pWTempString, string.c_str(), requiredSize); + if (size != (size_t)(-1)) + { + answer = pWTempString; + } + } + + if (pWTempString != NULL) + { + free(pWTempString); + } + + return answer; +} +#pragma endregion + +bool replace(std::string& str, const std::string& from, const std::string& to) { + size_t start_pos = str.find(from); + if (start_pos == std::string::npos) + return false; + str.replace(start_pos, from.length(), to); + return true; +} + +std::string replace2(const std::string str, const std::string& from, const std::string& to) { + std::string cpy = str; + size_t start_pos = cpy.find(from); + if (start_pos == std::string::npos) + return str; + cpy.replace(start_pos, from.length(), to); + return cpy; +} + +std::vector split(const std::string s, char seperator) +{ + std::vector output; + + std::string::size_type prev_pos = 0, pos = 0; + + while ((pos = s.find(seperator, pos)) != std::string::npos) + { + std::string substring(s.substr(prev_pos, pos - prev_pos)); + + output.push_back(substring); + + prev_pos = ++pos; + } + + output.push_back(s.substr(prev_pos, pos - prev_pos)); + + return output; +} + +bool isChild(std::string parent, std::string child) { + windowsPath(parent); + windowsPath(child); + tolower(parent); + tolower(child); + + return child.find(parent) != std::string::npos; +} + +// Parent full path, child full path, child name only +bool isParent(std::string parent, std::string child, std::string childName) { + windowsPath(parent); + windowsPath(child); + tolower(parent); + tolower(child); + tolower(childName); + + parent.append("\\"+ childName); + return parent == child; +} + +bool iequals(const std::string a, const std::string b) +{ + auto result = _stricmp(a.c_str(), b.c_str()); + return result == 0; +} + +bool equals(const std::string a, const std::string b) +{ + auto result = strcmp(a.c_str(), b.c_str()); + return result == 0; +} + +bool ends_with(std::string const& value, std::string const& ending) +{ + if (ending.size() > value.size()) return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + +bool starts_with(std::string str, std::string prefix) +{ + return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix); +} + +Platform::String^ convert(const std::string input) +{ + auto wstr = make_wstring(input); + Platform::String^ output = ref new Platform::String(wstr.c_str()); + return output; +} + +std::wstring convertToWString(const std::string input) +{ + auto wstr = make_wstring(input); + return wstr; +} + +std::string convert(Platform::String^ input) { + std::wstring wsstr(input->Data()); + auto utf8Str = make_string(wsstr); // UTF8-encoded text + + return utf8Str; +} + +std::string convert(std::wstring input) { + auto utf8Str = make_string(input); // UTF8-encoded text + + return utf8Str; +} + +std::string convert(const char* input) { + std::wstring wsstr((const wchar_t*)input); + auto utf8Str = make_string(wsstr); // UTF8-encoded text + + return utf8Str; +} + +LPCWSTR convertToLPCWSTR(std::string input) { + std::wstring stemp = make_wstring(input); + LPCWSTR sw = stemp.c_str(); + + return sw; +} + +LPCWSTR convertToLPCWSTR(Platform::String^ input) { + std::string inputString = convert(input); + std::wstring stemp = make_wstring(inputString); + LPCWSTR sw = stemp.c_str(); + + return sw; +} + +const char* convertToChar(Platform::String^ input) { + std::string output = convert(input); + return output.c_str(); +} + +void tolower(std::string& input) { + std::transform(input.begin(), input.end(), input.begin(), std::tolower); +} + +void tolower(Platform::String^ &input) { + std::string temp = convert(input); + tolower(temp); + input = convert(temp); +} + +void toupper(std::string& input) { + std::transform(input.begin(), input.end(), input.begin(), std::toupper); +} + +void toupper(Platform::String^& input) { + std::string temp = convert(input); + toupper(temp); + input = convert(temp); +} + +void windowsPath(std::string& path) { + std::replace(path.begin(), path.end(), '/', '\\'); +} + +void windowsPath(Platform::String^ &path) { + std::string temp = convert(path); + windowsPath(temp); + path = convert(temp); +} + +std::string merge(std::string targetFullPath, std::string subFullPath) { + size_t pos = subFullPath.rfind('\\'); + if (pos != std::string::npos) { + auto itemName = subFullPath.substr(pos + 1); + std::string newPath = targetFullPath + "\\" + itemName; + return newPath; + } + return targetFullPath; +} + +std::string& rtrim(std::string& s, const char* t) +{ + s.erase(s.find_last_not_of(t) + 1); + return s; +} + +// trim from beginning of string (left) +std::string& ltrim(std::string& s, const char* t) +{ + s.erase(0, s.find_first_not_of(t)); + return s; +} + +// trim from both ends of string (right then left) +std::string& trim(std::string& s, const char* t) +{ + return ltrim(rtrim(s, t), t); +} + +bool isWriteMode(const char* mode) { + return (!strcmp(mode, "w") || !strcmp(mode, "wb") || !strcmp(mode, "wt") || !strcmp(mode, "at") || !strcmp(mode, "a")); +} +bool isAppendMode(const char* mode) { + return (!strcmp(mode, "at") || !strcmp(mode, "a")); +} + +// Parent and child full path +std::string getSubRoot(std::string parent, std::string child) { + windowsPath(parent); + windowsPath(child); + auto childCut = child; + replace(childCut, (parent + "\\"), ""); + size_t len = childCut.find_first_of('\\', 0); + auto subRoot = childCut.substr(0, len); + + return parent + "\\"+ subRoot; +} diff --git a/UWP/UWPHelpers/StorageExtensions.h b/UWP/UWPHelpers/StorageExtensions.h new file mode 100644 index 0000000000..9045d13448 --- /dev/null +++ b/UWP/UWPHelpers/StorageExtensions.h @@ -0,0 +1,93 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +// Functions: +// replace(std::string& str, const std::string& from, const std::string& to) +// replace2(const std::string str, const std::string& from, const std::string& to) +// split(const std::string s, char seperator) +// +// isChild(std::string parent, std::string child) +// isParent(std::string parent, std::string child, std::string childName) +// +// iequals(const std::string a, const std::string b) case-insenstive +// equals(const std::string a, const std::string b) case-senstive +// ends_with(std::string const& value, std::string const& ending) +// +// convert(const std::string input) +// convertToWString(const std::string input) +// convert(Platform::String^ input) +// convert(std::wstring input) +// convert(const char* input) +// convertToLPCWSTR(Platform::String^ input) +// convertToLPCWSTR(std::string input) +// convertToChar(Platform::String^ input) +// +// tolower(std::string& input) +// tolower(Platform::String^ &input) +// toupper(std::string& input) +// toupper(Platform::String^& input) +// +// windowsPath(std::string& path) +// windowsPath(Platform::String^ &path) +// merge(std::string targetFullPath, std::string subFullPath) +// +// findInList(std::list& inputList, T& str) +// isWriteMode(const char* mode) +// getSubRoot(std::string parent, std::string child) + +#pragma once + +#include +#include +#include +#include + +bool replace(std::string& str, const std::string& from, const std::string& to); +std::string replace2(const std::string str, const std::string& from, const std::string& to); +std::vector split(const std::string s, char seperator); +// Parent full path, child full path +bool isChild(std::string parent, std::string child); +// Parent full path, child full path, child name only +bool isParent(std::string parent, std::string child, std::string childName); + +bool iequals(const std::string a, const std::string b); +bool equals(const std::string a, const std::string b); +bool ends_with(std::string const& value, std::string const& ending); +bool starts_with(std::string str, std::string prefix); + +Platform::String^ convert(const std::string input); +std::wstring convertToWString(const std::string input); +std::string convert(Platform::String^ input); +std::string convert(std::wstring input); +std::string convert(const char* input); +LPCWSTR convertToLPCWSTR(std::string input); +LPCWSTR convertToLPCWSTR(Platform::String^ input); +const char* convertToChar(Platform::String^ input); + +void tolower(std::string& input); +void tolower(Platform::String^& input); +void toupper(std::string& input); +void toupper(Platform::String^& input); + +void windowsPath(std::string& path); +void windowsPath(Platform::String^& path); + +std::string merge(std::string targetFullPath, std::string subFullPath); + +std::string& rtrim(std::string& s, const char* t = " \t\n\r\f\v"); +std::string& ltrim(std::string& s, const char* t = " \t\n\r\f\v"); +std::string& trim(std::string& s, const char* t = " \t\n\r\f\v"); + +template +bool findInList(std::list& inputList, T& str) { + return (std::find(inputList.begin(), inputList.end(), str) != inputList.end()); +}; + +bool isWriteMode(const char* mode); +bool isAppendMode(const char* mode); +// Parent and child full path +std::string getSubRoot(std::string parent, std::string child); + diff --git a/UWP/UWPHelpers/StorageFileW.h b/UWP/UWPHelpers/StorageFileW.h new file mode 100644 index 0000000000..7d5995339e --- /dev/null +++ b/UWP/UWPHelpers/StorageFileW.h @@ -0,0 +1,281 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +// Functions: +// IsValid() +// Delete() +// Rename(std::string name) +// Copy(StorageFolder^ folder) +// Move(StorageFolder^ folder) +// GetPath() +// GetName() +// Equal(std::string path) +// Equal(Path path) +// Equal(Platform::String^ path) +// Equal(StorageFile^ file) +// GetProperties() +// GetSize(bool updateCache) +// GetHandle(HANDLE* handle, HANDLE_ACCESS_OPTIONS access) +// GetStream(const char* mode) +// GetHandle(FILE* file) +// GetStorageFile() + +#pragma once + +#include "pch.h" +#include +#include + +#include "Common/Log.h" + +#include "StorageLog.h" +#include "StoragePath.h" +#include "StorageExtensions.h" +#include "StorageHandler.h" +#include "StorageAsync.h" +#include "StorageInfo.h" + +using namespace Platform; +using namespace Windows::Storage; +using namespace Windows::Foundation; +using namespace Windows::Storage::FileProperties; + +class StorageFileW { +public: + StorageFileW() { + } + + StorageFileW(StorageFile^ file) { + storageFile = file; + fileSize = 0; + } + StorageFileW(IStorageItem^ file) { + storageFile = (StorageFile^)file; + fileSize = 0; + } + ~StorageFileW() { + delete storageFile; + delete properties; + } + + // Detect if main storage file is not null + bool IsValid() { + return (storageFile != nullptr); + } + + // Delete file + bool Delete() { + bool state = ExecuteTask(storageFile->DeleteAsync()); + if (state) { + storageFile = nullptr; + } + return state; + } + + // Rename file + bool Rename(std::string name) { + auto path = PathUWP(name); + if (path.IsAbsolute()) { + name = path.GetFilename(); + } + return ExecuteTask(storageFile->RenameAsync(convert(name))); + } + + // Copy file + bool Copy(StorageFolder^ folder, std::string name) { + bool state = false; + StorageFile^ newFile; + ExecuteTask(newFile, storageFile->CopyAsync(folder, convert(name), NameCollisionOption::ReplaceExisting)); + if (newFile != nullptr) { + state = true; + } + return state; + } + + // Move file + bool Move(StorageFolder^ folder, std::string name) { + bool state = false; + IStorageItem^ newFile; + state = ExecuteTask(storageFile->MoveAsync(folder, convert(name), NameCollisionOption::GenerateUniqueName)); + if (state) { + ExecuteTask(newFile, folder->TryGetItemAsync(storageFile->Name)); + if (newFile != nullptr) { + storageFile = (StorageFile^)newFile; + } + else { + state = false; + } + } + return state; + } + + // Get file path + std::string GetPath() { + return convert(storageFile->Path); + } + + // Get file name + std::string GetName() { + return convert(storageFile->Name); + } + + // Compare file with std::string + bool Equal(std::string path) { + std::string filePath = GetPath(); + + // Fix slashs back from '/' to '\' + windowsPath(path); + return iequals(filePath, path); + } + + // Compare file with Platform::String + bool Equal(Platform::String^ path) { + return storageFile->Path->Equals(path); + } + + // Compare file with Path + bool Equal(PathUWP path) { + return Equal(path.ToString()); + } + + // Compare file with StorageFile + bool Equal(StorageFile^ file) { + return Equal(file->Path); + } + + // Get file size + __int64 GetSize(bool updateCache = false) { + if (fileSize == 0 || updateCache) { + // Let's try getting size by handle first + HANDLE handle; + HRESULT hr = GetHandle(&handle); + if (handle == INVALID_HANDLE_VALUE || hr != S_OK) { + // We have no other option, fallback to UWP + fileSize = FetchProperties()->Size; + } + else { + LARGE_INTEGER size{ 0 }; + if (FALSE == GetFileSizeEx(handle, &size)) { + LARGE_INTEGER end_offset; + const LARGE_INTEGER zero{}; + if (SetFilePointerEx(handle, zero, &end_offset, FILE_END) == 0) { + CloseHandle(handle); + } + else { + fileSize = end_offset.QuadPart; + SetFilePointerEx(handle, zero, nullptr, FILE_BEGIN); + CloseHandle(handle); + } + } + else { + fileSize = size.QuadPart; + CloseHandle(handle); + } + } + } + return fileSize; + } + + // Get file handle + HRESULT GetHandle(HANDLE* handle, int accessMode = GENERIC_READ, int shareMode = FILE_SHARE_READ) { + return GetFileHandle(storageFile, handle, GetAccessMode(accessMode), GetShareMode(shareMode)); + } + + // Get file stream + FILE* GetStream(const char* mode) { + HANDLE handle; + auto access = GENERIC_READ; + auto share = FILE_SHARE_READ; + bool isWrite = isWriteMode(mode); + if (isWrite) { + access = GENERIC_WRITE; + share = FILE_SHARE_WRITE; + } + HRESULT hr = GetHandle(&handle, access, share); + + FILE* file{}; + if (hr == S_OK && handle != INVALID_HANDLE_VALUE) { + int flags = _O_RDONLY; + if (isWrite) { + flags = _O_RDWR; + } + DEBUG_LOG(FILESYS, "Opening file (%s) with flag:%d mode:%s", GetPath().c_str(), flags, mode); + file = _fdopen(_open_osfhandle((intptr_t)handle, flags), mode); + } + return file; + } + + // Get file handle from stream + HANDLE GetHandle(FILE* file) { + return (HANDLE)_get_osfhandle(_fileno(file)); + } + + // Get file properties + FILE_BASIC_INFO* GetProperties() { + HANDLE handle; + HRESULT hr = GetHandle(&handle); + + size_t size = sizeof(FILE_BASIC_INFO); + FILE_BASIC_INFO* information = (FILE_BASIC_INFO*)(malloc(size)); + if(hr == S_OK && handle != INVALID_HANDLE_VALUE && information){ + information->FileAttributes = (DWORD)storageFile->Attributes; + + if (FALSE == GetFileInformationByHandleEx(handle, FileBasicInfo, information, (DWORD)size)) { + // Fallback to UWP method (Slow) + auto props = FetchProperties(); + information->ChangeTime.QuadPart = props->DateModified.UniversalTime; + information->CreationTime.QuadPart = props->ItemDate.UniversalTime; + information->LastAccessTime.QuadPart = props->DateModified.UniversalTime; + information->LastWriteTime.QuadPart = props->DateModified.UniversalTime; + } + CloseHandle(handle); + } + + return information; + } + + // Get main storage file + StorageFile^ GetStorageFile() { + return storageFile; + } + + time_t filetime_to_timet(LARGE_INTEGER ull) const { + return ull.QuadPart / 10000000ULL - 11644473600ULL; + } + ItemInfoUWP GetFileInfo() { + ItemInfoUWP info; + info.name = GetName(); + info.fullName = GetPath(); + info.isDirectory = false; + + auto sProperties = GetProperties(); + + info.size = (uint64_t)GetSize(); + info.lastAccessTime = (uint64_t)filetime_to_timet(sProperties->LastAccessTime); + info.lastWriteTime = (uint64_t)filetime_to_timet(sProperties->LastWriteTime); + info.changeTime = (uint64_t)filetime_to_timet(sProperties->ChangeTime); + info.creationTime = (uint64_t)filetime_to_timet(sProperties->CreationTime); + + + info.attributes = sProperties->FileAttributes; + + return info; + } + +private: + StorageFile^ storageFile; + BasicProperties^ properties; + __int64 fileSize = 0; + + BasicProperties^ FetchProperties() { + if (properties == nullptr) { + // Very bad and slow way in UWP to get size and other properties + // not preferred to be used on big list of files + ExecuteTask(properties, storageFile->GetBasicPropertiesAsync()); + } + return properties; + } +}; diff --git a/UWP/UWPHelpers/StorageFolderW.h b/UWP/UWPHelpers/StorageFolderW.h new file mode 100644 index 0000000000..0070be39bd --- /dev/null +++ b/UWP/UWPHelpers/StorageFolderW.h @@ -0,0 +1,570 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +// Functions: +// IsValid() +// GetPath() +// GetName() +// Delete() +// Equal(std::string path) +// Equal(Path path) +// Equal(Platform::String^ path) +// Equal(StorageFolder^ folder) +// GetSize(bool updateCache) +// CreateFolder(std::string name, bool replaceExisting) +// CreateFile(std::string name) +// Rename(std::string name) +// GetAllFiles(bool useWindowsIndexer) +// GetAllFolders(bool useWindowsIndexer) +// Contains(Path path, IStorageItem^& storageItem) +// Contains(std::string item) +// Copy(StorageFolderW folder) +// Move(StorageFolderW destination) +// GetHandle(HANDLE* handle, HANDLE_ACCESS_OPTIONS access) +// GetProperties() +// GetStorageFolder() +// GetFileStream(std::string name, const char* mode) + +#pragma once + +#include "pch.h" +#include + +#include "Common/Log.h" + +#include "StorageLog.h" +#include "StoragePath.h" +#include "StorageExtensions.h" +#include "StorageHandler.h" +#include "StorageAsync.h" +#include "StorageFileW.h" +#include "StorageInfo.h" + +using namespace Platform; +using namespace Windows::Storage; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::Storage::FileProperties; +using namespace Windows::Storage::Search; + +class StorageFolderW { +public: + StorageFolderW() { + } + StorageFolderW(StorageFolder^ folder) { + storageFolder = folder; + folderSize = 0; + } + StorageFolderW(IStorageItem^ folder) { + storageFolder = (StorageFolder^)folder; + folderSize = 0; + } + ~StorageFolderW() { + delete storageFolder; + delete properties; + } + + // Detect if storage folder is not null + bool IsValid() { + return (storageFolder != nullptr); + } + + // Get folder path + std::string GetPath() { + return convert(storageFolder->Path); + } + + // Delete folder + bool Delete() { + bool state = ExecuteTask(storageFolder->DeleteAsync()); + if (state) { + storageFolder = nullptr; + } + return state; + } + + // Compare folder with std::string + bool Equal(std::string path) { + std::string folderPath = GetPath(); + + // Fix slashs back from '/' to '\' + windowsPath(path); + return iequals(folderPath, path); + } + + // Compare folder with Platform::String + bool Equal(Platform::String^ path) { + return storageFolder->Path->Equals(path); + } + + // Compare folder with Path + bool Equal(PathUWP path) { + return Equal(path.ToString()); + } + + // Compare folder with StorageFolder + bool Equal(StorageFolder^ folder) { + return Equal(folder->Path); + } + + // Get folder name + std::string GetName() { + return convert(storageFolder->Name); + } + + // Create new folder + bool CreateFolder(std::string name, bool replaceExisting = true) { + bool state = false; + StorageFolder^ newFolder; + ExecuteTask(newFolder, storageFolder->CreateFolderAsync(convert(name), replaceExisting ? CreationCollisionOption::ReplaceExisting : CreationCollisionOption::GenerateUniqueName)); + + if (newFolder != nullptr) { + state = true; + } + + delete newFolder; + return state; + } + + // Create new file + bool CreateFile(std::string name, bool replaceExisting = true) { + bool state = false; + StorageFile^ newFile; + ExecuteTask(newFile, storageFolder->CreateFileAsync(convert(name), replaceExisting ? CreationCollisionOption::ReplaceExisting : CreationCollisionOption::GenerateUniqueName)); + + if (newFile != nullptr) { + state = true; + } + + delete newFile; + return state; + } + + // Rename folder + bool Rename(std::string name) { + auto path = PathUWP(name); + if (path.IsAbsolute()) { + name = path.GetFilename(); + } + return ExecuteTask(storageFolder->RenameAsync(convert(name))); + } + + // Get files 1st level only, no deep scan + std::list GetFiles() { + std::list files; + + IVectorView^ sFiles; + VERBOSE_LOG(FILESYS, "Getting files for %s", GetPath().c_str()); + + ExecuteTask(sFiles, storageFolder->GetFilesAsync()); + if (sFiles != nullptr) { + for (auto it = 0; it != sFiles->Size; ++it) { + auto sItem = sFiles->GetAt(it); + if (sItem != nullptr) { + files.push_back(StorageFileW(sItem)); + } + } + } + delete sFiles; + VERBOSE_LOG(FILESYS, "Total files added (%d) in (%s)", files.size(), GetPath().c_str()); + + return files; + } + + std::list GetFolders() { + std::list folders; + + IVectorView^ sFolders; + VERBOSE_LOG(FILESYS, "Getting folders for %s", GetPath().c_str()); + + ExecuteTask(sFolders, storageFolder->GetFoldersAsync()); + if (sFolders != nullptr) { + VERBOSE_LOG(FILESYS, "Sub folders founded (%d) in (%s)", sFolders->Size, GetPath().c_str()); + for (auto it = 0; it != sFolders->Size; ++it) { + auto sItem = sFolders->GetAt(it); + if (sItem != nullptr) { + folders.push_back(StorageFolderW(sItem)); + } + } + } + delete sFolders; + VERBOSE_LOG(FILESYS, "Total folders added (%d) in (%s)", folders.size(), GetPath().c_str()); + + return folders; + } + + // Get all files including files in sub folders (deep scan) + std::list GetAllFiles(bool useWindowsIndexer = false) { + std::list files; // No structure one-level list + + VERBOSE_LOG(FILESYS, "Getting all files for %s", GetPath().c_str()); + IVectorView^ sFiles; + + // Set query options to create groups of files within result + QueryOptions^ queryOptions = ref new QueryOptions(CommonFolderQuery::DefaultQuery); + queryOptions->FolderDepth = FolderDepth::Deep; // Search in all levels + + // Windows indexer is very bad, it will return missing results if the files recently copied + // It's better to search without it, even if it will be slower + queryOptions->IndexerOption = useWindowsIndexer ? IndexerOption::UseIndexerWhenAvailable : IndexerOption::DoNotUseIndexer; + StorageFileQueryResult^ filesResult = storageFolder->CreateFileQueryWithOptions(queryOptions); + + // Windows search query is slow but it's the only solution (or we need to build safe recursive function) + // Regular 'StorageFolder->GetFilesAsync()' will not search in sub dirs + ExecuteTask(sFiles, filesResult->GetFilesAsync()); + + if (sFiles != nullptr) { + for (auto it = 0; it != sFiles->Size; ++it) { + auto sItem = sFiles->GetAt(it); + if (sItem != nullptr) { + files.push_back(StorageFileW(sItem)); + } + } + } + delete sFiles; + + VERBOSE_LOG(FILESYS, "Total files added (%d) in (%s)", files.size(), GetPath().c_str()); + + return files; + } + + // Get all sub folders (deep scan) + std::list GetAllFolders(bool useWindowsIndexer = false) { + std::list folders; + + IVectorView^ sFolders; + VERBOSE_LOG(FILESYS, "Getting all folders for %s", GetPath().c_str()); + + // Set query options to create groups of files within result + QueryOptions^ queryOptions = ref new QueryOptions(CommonFolderQuery::DefaultQuery); + queryOptions->FolderDepth = FolderDepth::Deep; // Search in all levels + + // Windows indexer is very bad, it will return missing results if the files recently copied + // It's better to search without it, even if it will be slower + queryOptions->IndexerOption = useWindowsIndexer ? IndexerOption::UseIndexerWhenAvailable : IndexerOption::DoNotUseIndexer; + StorageFolderQueryResult^ foldersResult = storageFolder->CreateFolderQueryWithOptions(queryOptions); + + // Windows search query is slow but it's the only solution (or we need to build safe recursive function) + // Regular 'StorageFolder->GetFoldersAsync()' will not search in sub dirs + ExecuteTask(sFolders, foldersResult->GetFoldersAsync()); + if (sFolders != nullptr) { + VERBOSE_LOG(FILESYS, "Sub folders founded (%d) in (%s)", sFolders->Size, GetPath().c_str()); + for (auto it = 0; it != sFolders->Size; ++it) { + auto sItem = sFolders->GetAt(it); + if (sItem != nullptr) { + folders.push_back(StorageFolderW(sItem)); + } + } + } + delete sFolders; + VERBOSE_LOG(FILESYS, "Total folders added (%d) in (%s)", folders.size(), GetPath().c_str()); + + return folders; + } + + // Ensure item path without root + std::string CleanItemPath(PathUWP& path) { + std::string itemName = path.ToString(); + // Ensure slashs changed from '/' to '\' + windowsPath(itemName); + + if (path.IsAbsolute()) { + + // If full path detected item in sub location, + // root path must be removed + replace(itemName, convert(storageFolder->Path + "\\"), ""); + replace(itemName, GetPath(), ""); + } + + // Do some fixes because 'TryGetItemAsync' is very sensetive + replace(itemName, "\\\\", "\\"); + replace(itemName, "//", "/"); + replace(itemName, "*", ""); + rtrim(itemName, ":"); // remove ':' at the end of the path (if any) + + path = PathUWP(itemName); + + return itemName; + } + + // Check if folder contains item by name or path + bool Contains(PathUWP path, IStorageItem^& storageItem) { + auto pathString = CleanItemPath(path); + + // If the path is for parent then ignore + if (!path.IsAbsolute()) { + VERBOSE_LOG(FILESYS, "Looking for (%s) in (%s)", pathString.c_str(), GetPath().c_str()); + ExecuteTask(storageItem, storageFolder->TryGetItemAsync(convert(pathString))); + } + + return storageItem != nullptr; + } + + bool Contains(std::string path) { + IStorageItem^ tempItem; + return Contains(PathUWP(path), tempItem); + } + + void BuildStructure(StorageFolder^& folder, std::string path) { + std::string folderName; + std::vector locationParts = split(path, '\\'); + for each (auto dir in locationParts) { + folderName.append(dir); + // Create folder + ExecuteTask(folder, storageFolder->CreateFolderAsync(convert(folderName), CreationCollisionOption::OpenIfExists)); + folderName.append("\\"); + } + } + + void BuildStructure(StorageFolder^& folder, std::string path, StorageFolder^ target) { + std::string folderName; + std::vector locationParts = split(path, '\\'); + for each (auto dir in locationParts) { + folderName.append(dir); + // Create folder + ExecuteTask(folder, target->CreateFolderAsync(convert(folderName), CreationCollisionOption::OpenIfExists)); + folderName.append("\\"); + } + } + + StorageFolder^ GetOrCreateFolder(PathUWP path) { + StorageFolder^ folder; + auto pathString = CleanItemPath(path); + BuildStructure(folder, pathString); + + return folder; + } + + StorageFile^ GetOrCreateFile(PathUWP path) { + StorageFile^ file; + StorageFolder^ folder; + auto name = path.GetFilename(); + auto dir = PathUWP(path.GetDirectory()); + auto dirString = CleanItemPath(dir); + BuildStructure(folder, dirString); + if (folder != nullptr) { + ExecuteTask(file, folder->CreateFileAsync(convert(name), CreationCollisionOption::OpenIfExists)); + } + return file; + + } + + // Copy to another folder + bool Copy(StorageFolderW folder, bool move = false) { + auto files = GetAllFiles(); + bool state = false; + int failedCount = 0; + if (!files.empty()) { + auto destination = folder.GetStorageFolder(); + + // Copy files one by one to avoid 'access violation' issues with deep-level tasks + std::string rootName = convert(storageFolder->Name); + std::string rootPath = PathUWP(GetPath()).GetDirectory(); + windowsPath(rootPath); + + if (destination != nullptr) { + for each (auto file in files) { + auto fItem = file.GetStorageFile(); + + // Get file full path + std::string targetLocation = convert(fItem->Path); + // Remove root path but keep the parent name + replace(targetLocation, rootPath, ""); + replace(targetLocation, convert("\\" + fItem->Name), ""); + ltrim(targetLocation, "\\"); + rtrim(targetLocation, "\\"); + + // Build folder structure + StorageFolder^ targetFolder; + BuildStructure(targetFolder, targetLocation, destination); + + if (targetFolder != nullptr) { + // Copy file + StorageFile^ testFile; + if (move) { + ExecuteTask(fItem->MoveAsync((IStorageFolder^)targetFolder, fItem->Name, NameCollisionOption::ReplaceExisting)); + ExecuteTask(testFile, targetFolder->GetFileAsync(fItem->Name)); // testing, it can be ignored + } + else { + ExecuteTask(testFile, fItem->CopyAsync((IStorageFolder^)targetFolder, fItem->Name, NameCollisionOption::ReplaceExisting)); + } + + if (testFile == nullptr) { + // File failed to copy, we can handle this later + failedCount++; + } + } + } + // Try to get the new folder + IStorageItem^ newFolder; + ExecuteTask(newFolder, destination->TryGetItemAsync(convert(rootName))); + if (newFolder != nullptr) { + if (move) { + if (failedCount == 0) { + // If all files moved, we can safely remove the folder + ExecuteTask(storageFolder->DeleteAsync()); + } + storageFolder = (StorageFolder^)newFolder; + } + state = true; + } + } + } + return state; + } + + // Copy to another folder using StorageFolder^ + bool Copy(StorageFolder^ folder, bool move = false) { + return Copy(StorageFolderW(folder), move); + } + + // Move to another folder + bool Move(StorageFolderW destination) { + return Copy(destination, true); + } + + // Get storage folder handle + HRESULT GetHandle(HANDLE* handle, int accessMode = GENERIC_READ, int shareMode = FILE_SHARE_READ) { + return GetFolderHandle(storageFolder, handle, GetAccessMode(accessMode),GetShareMode(shareMode)); + } + + // Get storage folder handle + HRESULT GetHandleForFile(HANDLE* handle, std::string filename, int accessMode = GENERIC_READ, int shareMode = FILE_SHARE_READ, int openMode = OPEN_EXISTING) { + return GetFileHandleFromFolder(storageFolder, filename, handle, GetAccessMode(accessMode), GetShareMode(shareMode), GetOpenMode(openMode)); + } + + // Get file stream + FILE* GetFileStream(std::string name, const char* mode) { + FILE* file{}; + + bool createIfNotExists = isWriteMode(mode); + bool isAppend = isAppendMode(mode); + + StorageFile^ sfile; + + if (createIfNotExists) { + auto createMode = isAppend ? CreationCollisionOption::OpenIfExists : CreationCollisionOption::ReplaceExisting; + ExecuteTask(sfile, storageFolder->CreateFileAsync(convert(name), createMode)); + } + else { + IStorageItem^ tempItem; + ExecuteTask(tempItem, storageFolder->TryGetItemAsync(convert(name))); + sfile = (StorageFile^)tempItem; + } + + StorageFileW storageFile(sfile); + if (storageFile.IsValid()) { + file = storageFile.GetStream(mode); + } + + return file; + } + + // Get folder size + __int64 GetSize(bool updateCache = false) { + if (folderSize == 0 || updateCache) { + // Let's try getting size by handle first + HANDLE handle; + HRESULT hr = GetHandle(&handle); + if (handle == INVALID_HANDLE_VALUE || hr != S_OK) { + // We have no other option, fallback to UWP + // This need to sum all files inside + auto files = GetAllFiles(true); + for each (auto file in files) { + folderSize += file.GetSize(); + } + } + else { + LARGE_INTEGER size{ 0 }; + if (FALSE == GetFileSizeEx(handle, &size)) { + LARGE_INTEGER end_offset; + const LARGE_INTEGER zero{}; + if (SetFilePointerEx(handle, zero, &end_offset, FILE_END) == 0) { + CloseHandle(handle); + } + else { + folderSize = end_offset.QuadPart; + SetFilePointerEx(handle, zero, nullptr, FILE_BEGIN); + CloseHandle(handle); + } + } + else { + folderSize = size.QuadPart; + CloseHandle(handle); + } + } + + } + return folderSize; + } + + // Get folder basic properties + FILE_BASIC_INFO* GetProperties() { + HANDLE handle; + HRESULT hr = GetHandle(&handle); + + size_t size = sizeof(FILE_BASIC_INFO); + FILE_BASIC_INFO* information = (FILE_BASIC_INFO*)(malloc(size)); + if (hr == S_OK && handle != INVALID_HANDLE_VALUE && information) { + information->FileAttributes = (DWORD)storageFolder->Attributes; + + if (FALSE == GetFileInformationByHandleEx(handle, FileBasicInfo, information, (DWORD)size)) { + // Fallback to UWP method (Slow) + auto props = FetchProperties(); + information->ChangeTime.QuadPart = props->DateModified.UniversalTime; + information->CreationTime.QuadPart = props->ItemDate.UniversalTime; + information->LastAccessTime.QuadPart = props->DateModified.UniversalTime; + information->LastWriteTime.QuadPart = props->DateModified.UniversalTime; + } + CloseHandle(handle); + } + + return information; + } + + // Get main storage folder + StorageFolder^ GetStorageFolder() { + return storageFolder; + } + + time_t filetime_to_timet(LARGE_INTEGER ull) const { + return ull.QuadPart / 10000000ULL - 11644473600ULL; + } + ItemInfoUWP GetFolderInfo() { + ItemInfoUWP info; + info.name = GetName(); + info.fullName = GetPath(); + info.isDirectory = true; + + + auto sProperties = GetProperties(); + + info.size = (uint64_t)GetSize(); + info.lastAccessTime = (uint64_t)filetime_to_timet(sProperties->LastAccessTime); + info.lastWriteTime = (uint64_t)filetime_to_timet(sProperties->LastWriteTime); + info.changeTime = (uint64_t)filetime_to_timet(sProperties->ChangeTime); + info.creationTime = (uint64_t)filetime_to_timet(sProperties->CreationTime); + + + info.attributes = sProperties->FileAttributes; + + return info; + } + +private: + StorageFolder^ storageFolder; + BasicProperties^ properties; + __int64 folderSize = 0; + + BasicProperties^ FetchProperties() { + if (properties == nullptr) { + // Very bad and slow way in UWP to get size and other properties + // not preferred to be used on big list of files + ExecuteTask(properties, storageFolder->GetBasicPropertiesAsync()); + } + return properties; + } +}; diff --git a/UWP/UWPHelpers/StorageHandler.cpp b/UWP/UWPHelpers/StorageHandler.cpp new file mode 100644 index 0000000000..d053a21b66 --- /dev/null +++ b/UWP/UWPHelpers/StorageHandler.cpp @@ -0,0 +1,132 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan + +// Functions: +// GetFileHandle(StorageFile^ file, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode, HANDLE_SHARING_OPTIONS shareMode) +// GetFileHandleFromFolder(StorageFolder^ folder, std::string filename, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode, HANDLE_SHARING_OPTIONS shareMode, HANDLE_CREATION_OPTIONS openMode) +// GetFolderHandle(StorageFolder^ folder, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode, HANDLE_SHARING_OPTIONS shareMode) +// +// GetAccessMode(int accessMode) +// GetShareMode(int shareMode) +// GetOpenMode(int openMode) + +#include "StorageHandler.h" +#include "StorageExtensions.h" + +using namespace Windows::Storage; + +HRESULT GetFileHandle(StorageFile^ file, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode, HANDLE_SHARING_OPTIONS shareMode) +{ + if (file != nullptr) { + Microsoft::WRL::ComPtr abiPointer(reinterpret_cast(file)); + Microsoft::WRL::ComPtr handleAccess; + if (SUCCEEDED(abiPointer.As(&handleAccess))) + { + HANDLE hFile = INVALID_HANDLE_VALUE; + + if (SUCCEEDED(handleAccess->Create(accessMode, + shareMode, + HO_NONE, + nullptr, + &hFile))) + { + *handle = hFile; + return S_OK; + } + } + } + return E_FAIL; +} + +HRESULT GetFileHandleFromFolder(StorageFolder^ folder, std::string filename, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode, HANDLE_SHARING_OPTIONS shareMode, HANDLE_CREATION_OPTIONS openMode) +{ + if (folder != nullptr) { + Microsoft::WRL::ComPtr abiPointer(reinterpret_cast(folder)); + Microsoft::WRL::ComPtr handleAccess; + if (SUCCEEDED(abiPointer.As(&handleAccess))) + { + HANDLE hFolder = INVALID_HANDLE_VALUE; + auto fn = convertToLPCWSTR(filename); + if (SUCCEEDED(handleAccess->Create(fn, + openMode, + accessMode, + shareMode, + HO_NONE, + nullptr, + &hFolder))) + { + *handle = hFolder; + return S_OK; + } + } + } + return E_FAIL; +} + +HRESULT GetFolderHandle(StorageFolder^ folder, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode, HANDLE_SHARING_OPTIONS shareMode) +{ + if (folder != nullptr) { + Microsoft::WRL::ComPtr abiPointer(reinterpret_cast(folder)); + Microsoft::WRL::ComPtr handleAccess; + if (SUCCEEDED(abiPointer.As(&handleAccess))) + { + HANDLE hFolder = INVALID_HANDLE_VALUE; + + if (SUCCEEDED(handleAccess->Create(accessMode, + shareMode, + HO_NONE, + nullptr, + &hFolder))) + { + *handle = hFolder; + return S_OK; + } + } + } + return E_FAIL; +} + +HANDLE_ACCESS_OPTIONS GetAccessMode(int accessMode) { + switch (accessMode) { + case GENERIC_READ: + return HAO_READ | HAO_READ_ATTRIBUTES; + case GENERIC_WRITE: + return HAO_WRITE | HAO_READ; + case GENERIC_ALL: + return HAO_READ | HAO_READ_ATTRIBUTES | HAO_WRITE | HAO_DELETE; + default: + return HAO_READ; + } +} + +HANDLE_SHARING_OPTIONS GetShareMode(int shareMode) { + switch (shareMode) + { + case FILE_SHARE_READ: + return HSO_SHARE_READ; + case FILE_SHARE_WRITE: + return HSO_SHARE_READ | HSO_SHARE_WRITE; + case FILE_SHARE_DELETE: + return HSO_SHARE_DELETE; + default: + return HSO_SHARE_READ; + } +} + +HANDLE_CREATION_OPTIONS GetOpenMode(int openMode) { + switch (openMode) + { + case CREATE_NEW: + return HCO_CREATE_NEW; + case CREATE_ALWAYS: + return HCO_CREATE_ALWAYS; + case OPEN_ALWAYS: + return HCO_OPEN_ALWAYS; + case OPEN_EXISTING: + return HCO_OPEN_EXISTING; + default: + return HCO_OPEN_EXISTING; + } +} diff --git a/UWP/UWPHelpers/StorageHandler.h b/UWP/UWPHelpers/StorageHandler.h new file mode 100644 index 0000000000..1b707aacb8 --- /dev/null +++ b/UWP/UWPHelpers/StorageHandler.h @@ -0,0 +1,137 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan + +// Functions: +// GetFileHandle(StorageFile^ file, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode, HANDLE_SHARING_OPTIONS shareMode) +// GetFileHandleFromFolder(StorageFolder^ folder, std::string filename, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode, HANDLE_SHARING_OPTIONS shareMode, HANDLE_CREATION_OPTIONS openMode) +// GetFolderHandle(StorageFolder^ folder, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode, HANDLE_SHARING_OPTIONS shareMode) +// +// GetAccessMode(int accessMode) +// GetShareMode(int shareMode) +// GetOpenMode(int openMode) + +#pragma once + +#include "pch.h" +#include +#include +#include +#include + +using namespace Windows::Storage; + +#pragma region WindowsStorageCOM +// These APIs have been accidentally placed inside the WINAPI_PARTITION_DESKTOP partition +// (they're not desktop-specific; they're available to UWPs). +// This will be addressed in future SDK updates. +// These are copied from WindowsStorageCOM.h +// You can remove this region once the real file has been updated +// to fix the WINAPI_PARTITION_DESKTOP block +// Source: https://stackoverflow.com/questions/42799235/how-can-i-get-a-win32-handle-for-a-storagefile-or-storagefolder-in-uwp + +typedef interface IOplockBreakingHandler IOplockBreakingHandler; +typedef interface IStorageItemHandleAccess IStorageItemHandleAccess; +typedef interface IStorageFolderHandleAccess IStorageFolderHandleAccess; + +#ifdef __cplusplus +extern "C" { +#endif + + typedef + enum HANDLE_OPTIONS + { + HO_NONE = 0, + HO_OPEN_REQUIRING_OPLOCK = 0x40000, + HO_DELETE_ON_CLOSE = 0x4000000, + HO_SEQUENTIAL_SCAN = 0x8000000, + HO_RANDOM_ACCESS = 0x10000000, + HO_NO_BUFFERING = 0x20000000, + HO_OVERLAPPED = 0x40000000, + HO_WRITE_THROUGH = 0x80000000 + } HANDLE_OPTIONS; + + DEFINE_ENUM_FLAG_OPERATORS(HANDLE_OPTIONS); + typedef + enum HANDLE_ACCESS_OPTIONS + { + HAO_NONE = 0, + HAO_READ_ATTRIBUTES = 0x80, + HAO_READ = 0x120089, + HAO_WRITE = 0x120116, + HAO_DELETE = 0x10000 + } HANDLE_ACCESS_OPTIONS; + + DEFINE_ENUM_FLAG_OPERATORS(HANDLE_ACCESS_OPTIONS); + typedef + enum HANDLE_SHARING_OPTIONS + { + HSO_SHARE_NONE = 0, + HSO_SHARE_READ = 0x1, + HSO_SHARE_WRITE = 0x2, + HSO_SHARE_DELETE = 0x4 + } HANDLE_SHARING_OPTIONS; + + DEFINE_ENUM_FLAG_OPERATORS(HANDLE_SHARING_OPTIONS); + typedef + enum HANDLE_CREATION_OPTIONS + { + HCO_CREATE_NEW = 0x1, + HCO_CREATE_ALWAYS = 0x2, + HCO_OPEN_EXISTING = 0x3, + HCO_OPEN_ALWAYS = 0x4, + HCO_TRUNCATE_EXISTING = 0x5 + } HANDLE_CREATION_OPTIONS; + + + EXTERN_C const IID IID_IOplockBreakingHandler; + MIDL_INTERFACE("826ABE3D-3ACD-47D3-84F2-88AAEDCF6304") + IOplockBreakingHandler : public IUnknown + { + public: + virtual HRESULT STDMETHODCALLTYPE OplockBreaking(void) = 0; + + }; + + EXTERN_C const IID IID_IStorageItemHandleAccess; + MIDL_INTERFACE("5CA296B2-2C25-4D22-B785-B885C8201E6A") + IStorageItemHandleAccess : public IUnknown + { + public: + virtual HRESULT STDMETHODCALLTYPE Create( + HANDLE_ACCESS_OPTIONS accessOptions, + HANDLE_SHARING_OPTIONS sharingOptions, + HANDLE_OPTIONS options, + __RPC__in_opt IOplockBreakingHandler * oplockBreakingHandler, + __RPC__deref_out_opt HANDLE * interopHandle) = 0; + + }; + + EXTERN_C const IID IID_IStorageFolderHandleAccess; + MIDL_INTERFACE("DF19938F-5462-48A0-BE65-D2A3271A08D6") + IStorageFolderHandleAccess : public IUnknown + { + public: + virtual HRESULT STDMETHODCALLTYPE Create( + __RPC__in_string LPCWSTR fileName, + HANDLE_CREATION_OPTIONS creationOptions, + HANDLE_ACCESS_OPTIONS accessOptions, + HANDLE_SHARING_OPTIONS sharingOptions, + HANDLE_OPTIONS options, + __RPC__in_opt IOplockBreakingHandler * oplockBreakingHandler, + __RPC__deref_out_opt HANDLE * interopHandle) = 0; + + }; +#ifdef __cplusplus +} +#endif +#pragma endregion + +HRESULT GetFileHandle(StorageFile^ file, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode, HANDLE_SHARING_OPTIONS shareMode); +HRESULT GetFileHandleFromFolder(StorageFolder^ folder, std::string filename, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode, HANDLE_SHARING_OPTIONS shareMode, HANDLE_CREATION_OPTIONS openMode); +HRESULT GetFolderHandle(StorageFolder^ folder, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode, HANDLE_SHARING_OPTIONS shareMode); + +HANDLE_ACCESS_OPTIONS GetAccessMode(int accessMode); +HANDLE_SHARING_OPTIONS GetShareMode(int shareMode); +HANDLE_CREATION_OPTIONS GetOpenMode(int openMode); diff --git a/UWP/UWPHelpers/StorageInfo.h b/UWP/UWPHelpers/StorageInfo.h new file mode 100644 index 0000000000..52fee32949 --- /dev/null +++ b/UWP/UWPHelpers/StorageInfo.h @@ -0,0 +1,24 @@ +// UWP STORAGE MANAGER +// For updates check: https://github.com/basharast/UWP2Win32 + +#pragma once + +#include +#include +#include +#include + +struct ItemInfoUWP { + std::string name; + std::string fullName; + + bool isDirectory = false; + + uint64_t size = 0; + uint64_t lastAccessTime = 0; + uint64_t lastWriteTime = 0; + uint64_t changeTime = 0; + uint64_t creationTime = 0; + + DWORD attributes = 0; +}; diff --git a/UWP/UWPHelpers/StorageItemW.h b/UWP/UWPHelpers/StorageItemW.h new file mode 100644 index 0000000000..32b63acf55 --- /dev/null +++ b/UWP/UWPHelpers/StorageItemW.h @@ -0,0 +1,324 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +#pragma once + +#include "pch.h" +#include +#include + +#include "StorageLog.h" +#include "StoragePath.h" +#include "StorageExtensions.h" +#include "StorageHandler.h" +#include "StorageAsync.h" +#include "StorageFolderW.h" + +using namespace Platform; +using namespace Windows::Storage; +using namespace Windows::Foundation; +using namespace Windows::Storage::FileProperties; + +class StorageItemW { +public: + StorageItemW() { + } + + StorageItemW(IStorageItem^ item) { + storageItem = item; + itemSize = 0; + if (item != nullptr) { + isDirectory = storageItem->IsOfType(StorageItemTypes::Folder); + if (isDirectory) { + storageFolderW = StorageFolderW(item); + } + else { + storageFileW = StorageFileW(item); + } + } + } + StorageItemW(StorageFolderW folder) { + StorageItemW((IStorageItem^)folder.GetStorageFolder()); + } + StorageItemW(StorageFileW file) { + StorageItemW((IStorageItem^)file.GetStorageFile()); + } + ~StorageItemW() { + delete storageItem; + } + + bool IsDirectory() { + return isDirectory; + } + + // Detect if main storage item is not null + bool IsValid() { + return (storageItem != nullptr); + } + + // Delete item + bool Delete() { + bool state = ExecuteTask(storageItem->DeleteAsync()); + if (state) { + storageItem = nullptr; + } + return state; + } + + // Rename item + bool Rename(std::string name) { + auto path = PathUWP(name); + if (path.IsAbsolute()) { + name = path.GetFilename(); + } + return ExecuteTask(storageItem->RenameAsync(convert(name))); + } + + // Copy item + bool Copy(StorageFolder^ folder) { + bool state = false; + if (IsDirectory()) { + state = storageFolderW.Copy(folder); + } + else { + state = storageFileW.Copy(folder, storageFileW.GetName()); + } + return state; + } + + + // Copy item + bool Copy(StorageItemW folder) { + return Copy(folder.GetStorageFolder()); + } + + bool Copy(StorageFolder^ folder, std::string name) { + bool state = false; + if (IsDirectory()) { + state = storageFolderW.Copy(folder); + } + else { + state = storageFileW.Copy(folder, name); + } + return state; + } + + bool Copy(StorageItemW folder, std::string name) { + return Copy(folder.GetStorageFolder(), name); + } + + // Move item + bool Move(StorageFolder^ folder) { + bool state = false; + if (IsDirectory()) { + state = storageFolderW.Copy(folder, true); + } + else { + state = storageFileW.Move(folder, storageFileW.GetName()); + } + return state; + } + + bool Move(StorageItemW folder) { + return Move(folder.GetStorageFolder()); + } + + // Move item + bool Move(StorageFolder^ folder, std::string name) { + bool state = false; + if (IsDirectory()) { + state = storageFolderW.Copy(folder, true); + } + else { + state = storageFileW.Move(folder, name); + } + return state; + } + + bool Move(StorageItemW folder, std::string name) { + return Move(folder.GetStorageFolder(), name); + } + + // Get item path + std::string GetPath() { + return convert(storageItem->Path); + } + + // Get item name + std::string GetName() { + return convert(storageItem->Name); + } + + // Compare item with std::string + bool Equal(std::string path) { + std::string itemPath = GetPath(); + + // Fix slashs back from '/' to '\' + windowsPath(path); + return iequals(itemPath, path); + } + + // Compare item with Platform::String + bool Equal(Platform::String^ path) { + return storageItem->Path->Equals(path); + } + + // Compare item with Path + bool Equal(PathUWP path) { + return Equal(path.ToString()); + } + + // Compare item with StorageItem + bool Equal(IStorageItem^ item) { + return Equal(item->Path); + } + + // Get item size + __int64 GetSize(bool updateCache = false) { + if (itemSize == 0 || updateCache) { + if (IsDirectory()) { + itemSize = storageFolderW.GetSize(updateCache); + } + else { + itemSize = storageFileW.GetSize(updateCache); + } + } + return itemSize; + } + + // Get item handle + HRESULT GetHandle(HANDLE* handle, int accessMode = GENERIC_READ, int shareMode = FILE_SHARE_READ) { + HRESULT hr = E_FAIL; + if(IsDirectory()){ + hr = GetFolderHandle(storageFolderW.GetStorageFolder(), handle, GetAccessMode(accessMode), GetShareMode(shareMode)); + } + else { + hr = GetFileHandle(storageFileW.GetStorageFile(), handle, GetAccessMode(accessMode), GetShareMode(shareMode)); + } + return hr; + } + + // Get file stream + FILE* GetStream(const char* mode) { + FILE* file{}; + if (!IsDirectory()) { + file = storageFileW.GetStream(mode); + } + return file; + } + + // Get files stream from folder + FILE* GetFileStream(std::string name, const char* mode) { + FILE* file{}; + if (IsDirectory()) { + file = storageFolderW.GetFileStream(name, mode); + } + return file; + } + + // Get item handle from stream + HANDLE GetHandleFromStream(FILE* file) { + return (HANDLE)_get_osfhandle(_fileno(file)); + } + + + // Create or open folder if exists + StorageFolderW CreateFolder(PathUWP path) { + return StorageFolderW(storageFolderW.GetOrCreateFolder(path)); + } + + // Create or open file if exists + StorageFileW CreateFile(PathUWP path) { + return StorageFileW(storageFolderW.GetOrCreateFile(path)); + } + + // Create new folder + bool CreateFolder(std::string name, bool replaceExisting = true) { + return storageFolderW.CreateFolder(name, replaceExisting); + } + + // Create new file + bool CreateFile(std::string name, bool replaceExisting = true) { + return storageFolderW.CreateFile(name, replaceExisting); + } + + // Check if folder contains item by name or path + bool Contains(PathUWP path, IStorageItem^& storageItem) { + return storageFolderW.Contains(path, storageItem); + } + + // Get all sub folders (deep scan) + std::list GetAllFolders(bool useWindowsIndexer = false) { + return storageFolderW.GetAllFolders(useWindowsIndexer); + } + + // Get all files including files in sub folders (deep scan) + std::list GetAllFiles(bool useWindowsIndexer = false) { + return storageFolderW.GetAllFiles(useWindowsIndexer); + } + + std::list GetFolders() { + return storageFolderW.GetFolders(); + } + + std::list GetFiles() { + return storageFolderW.GetFiles(); + } + + // Get item properties + FILE_BASIC_INFO* GetProperties() { + if (IsDirectory()) { + return storageFolderW.GetProperties(); + } + else { + return storageFileW.GetProperties(); + } + } + + // Get main storage item + IStorageItem^ GetStorageItem() { + return storageItem; + } + + // Get Wrapped StorageFolder + StorageFolderW GetStorageFolderW() { + return storageFolderW; + } + + // Get Wrapped StorageFolder + StorageFolder^ GetStorageFolder() { + return storageFolderW.GetStorageFolder(); + } + + // Get Wrapped StorageFile + StorageFileW GetStorageFileW() { + return storageFileW; + } + + // Get StorageFile^ + StorageFile^ GetStorageFile() { + return storageFileW.GetStorageFile(); + } + + ItemInfoUWP GetItemInfo() { + ItemInfoUWP info; + if (IsDirectory()) { + info = storageFolderW.GetFolderInfo(); + } + else { + info = storageFileW.GetFileInfo(); + } + return info; + } + +private: + IStorageItem^ storageItem; + StorageFileW storageFileW; + StorageFolderW storageFolderW; + + __int64 itemSize = 0; + bool isDirectory = false; +}; diff --git a/UWP/UWPHelpers/StorageLog.h b/UWP/UWPHelpers/StorageLog.h new file mode 100644 index 0000000000..3c427d1a74 --- /dev/null +++ b/UWP/UWPHelpers/StorageLog.h @@ -0,0 +1,17 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +#pragma once + +// Link your debug function below +// IGNORE THIS FILE FOR PPSSPP +// WE ALREADY USING PPSSPP's LOGS FUNCTIONS +#define UWP_ERROR_LOG(t,...) do { } while (false) +#define UWP_WARN_LOG(t,...) do { } while (false) +#define UWP_NOTICE_LOG(t,...) do { } while (false) +#define UWP_INFO_LOG(t,...) do { } while (false) +#define UWP_DEBUG_LOG(t,...) do { } while (false) +#define UWP_VERBOSE_LOG(t,...) do { } while (false) diff --git a/UWP/UWPHelpers/StorageManager.cpp b/UWP/UWPHelpers/StorageManager.cpp new file mode 100644 index 0000000000..2c3592f152 --- /dev/null +++ b/UWP/UWPHelpers/StorageManager.cpp @@ -0,0 +1,869 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +// Functions: +// GetWorkingFolder() +// SetWorkingFolder(std::string location) +// GetInstallationFolder() +// GetLocalFolder() +// GetTempFolder() +// GetPicturesFolder() +// GetVideosFolder() +// GetDocumentsFolder() +// GetMusicFolder() +// GetPreviewPath(std::string path) +// +// CreateFileUWP(std::string path, int accessMode, int shareMode, int openMode) +// CreateFileUWP(std::wstring path, int accessMode, int shareMode, int openMode) +// GetFileStream(std::string path, const char* mode) +// IsValidUWP(std::string path) +// IsExistsUWP(std::string path) +// IsDirectoryUWP(std::string path) +// +// GetFolderContents(std::string path, T& files) +// GetFolderContents(std::wstring path, T& files) +// GetFileInfoUWP(std::string path, T& info) +// +// GetSizeUWP(std::string path) +// DeleteUWP(std::string path) +// CreateDirectoryUWP(std::string path, bool replaceExisting) +// RenameUWP(std::string path, std::string name) +// CopyUWP(std::string path, std::string name) +// MoveUWP(std::string path, std::string name) +// +// OpenFile(std::string path) +// OpenFolder(std::string path) +// IsFirstStart() +// +// GetLogFile() +// SaveLogs() +// CleanupLogs() + +#include "pch.h" + +#include "Common/Log.h" + +#include "StorageConfig.h" +#include "StorageManager.h" +#include "StorageExtensions.h" +#include "StorageHandler.h" +#include "StorageAsync.h" +#include "StorageAccess.h" +#include "StorageItemW.h" +#include "StorageLog.h" + + +using namespace Platform; +using namespace Windows::Storage; +using namespace Windows::Foundation; +using namespace Windows::ApplicationModel; + +extern std::list FutureAccessItems; + +#pragma region Locations +std::string GetWorkingFolder() { + if (AppWorkingFolder.empty()) { + return GetLocalFolder(); + } + else { + return AppWorkingFolder; + } +} +void SetWorkingFolder(std::string location) { + AppWorkingFolder = location; +} +std::string GetInstallationFolder() { + return convert(Package::Current->InstalledLocation->Path); +} +StorageFolder^ GetLocalStorageFolder() { + return ApplicationData::Current->LocalFolder; +} +std::string GetLocalFolder() { + return convert(GetLocalStorageFolder()->Path); +} +std::string GetTempFolder() { + return convert(ApplicationData::Current->TemporaryFolder->Path); +} +std::string GetTempFile(std::string name) { + StorageFile^ tmpFile; + ExecuteTask(tmpFile, ApplicationData::Current->TemporaryFolder->CreateFileAsync(convert(name), CreationCollisionOption::GenerateUniqueName)); + if (tmpFile != nullptr) { + return convert(tmpFile->Path); + } + else { + return ""; + } +} +std::string GetPicturesFolder() { + // Requires 'picturesLibrary' capability + return convert(KnownFolders::PicturesLibrary->Path); +} +std::string GetVideosFolder() { + // Requires 'videosLibrary' capability + return convert(KnownFolders::VideosLibrary->Path); +} +std::string GetDocumentsFolder() { + // Requires 'documentsLibrary' capability + return convert(KnownFolders::DocumentsLibrary->Path); +} +std::string GetMusicFolder() { + // Requires 'musicLibrary' capability + return convert(KnownFolders::MusicLibrary->Path); +} +std::string GetPreviewPath(std::string path) { + std::string pathView = path; + windowsPath(pathView); + replace(pathView, GetLocalFolder(), "LocalState"); + replace(pathView, GetTempFolder(), "TempState"); + replace(pathView, GetInstallationFolder(), "Installation folder"); + return pathView; +} +bool isLocalState(std::string path) { + return iequals(GetPreviewPath(path), "LocalState"); +} +#pragma endregion + +#pragma region Internal +PathUWP PathResolver(PathUWP path) { + auto root = path.GetDirectory(); + auto newPath = path.ToString(); + if (path.IsRoot() || iequals(root, "/") || iequals(root, "\\")) { + // System requesting file from app data + replace(newPath, "/", (GetLocalFolder() + (path.size() > 1 ? "/": ""))); + } + path = PathUWP(newPath); + return path; +} +PathUWP PathResolver(std::string path) { + return PathResolver(PathUWP(path)); +} + +std::string ResolvePathUWP(std::string path) { + return PathResolver(path).ToString(); +} + +// Return closer parent +StorageItemW GetStorageItemParent(PathUWP path) { + path = PathResolver(path); + StorageItemW parent; + + for (auto& fItem : FutureAccessItems) { + if (isChild(fItem.GetPath(), path.ToString())) { + if (fItem.IsDirectory()) { + parent = fItem; + break; + } + } + } + + return parent; +} + +StorageItemW GetStorageItem(PathUWP path, bool createIfNotExists = false, bool forceFolderType = false) { + // Fill call will be ignored internally after the first call + FillLookupList(); + + path = PathResolver(path); + StorageItemW item; + + // Look for match in FutureAccessItems + for (auto& fItem : FutureAccessItems) { + if (fItem.Equal(path)) { + item = fItem; + break; + } + } + + if (!item.IsValid()) { + // Look for match inside FutureAccessFolders + for (auto& fItem : FutureAccessItems) { + if (fItem.IsDirectory()) { + IStorageItem^ storageItem; + if (fItem.Contains(path, storageItem)) { + item = StorageItemW(storageItem); + break; + } + } + } + } + + if (!item.IsValid() && createIfNotExists) { + // Create and return new folder + auto parent = GetStorageItemParent(path); + if (parent.IsValid()) { + if (!forceFolderType) { + // File creation must be called in this case + // Create folder usually will be called from 'CreateDirectory' + item = StorageItemW(parent.CreateFile(path)); + } + else { + item = StorageItemW(parent.CreateFolder(path)); + } + } + } + return item; +} + +StorageItemW GetStorageItem(std::string path, bool createIfNotExists = false, bool forceFolderType = false) { + return GetStorageItem(PathUWP(path), createIfNotExists, forceFolderType); +} + +std::list GetStorageItemsByParent(PathUWP path) { + path = PathResolver(path); + std::list items; + + // Look for match in FutureAccessItems + for (auto& fItem : FutureAccessItems) { + if (isParent(path.ToString(), fItem.GetPath(), fItem.GetName())) { + items.push_back(fItem); + } + } + + return items; +} + +std::list GetStorageItemsByParent(std::string path) { + return GetStorageItemsByParent(PathUWP(path)); +} + +bool IsContainsAccessibleItems(PathUWP path) { + path = PathResolver(path); + + for (auto& fItem : FutureAccessItems) { + if (isParent(path.ToString(), fItem.GetPath(), fItem.GetName())) { + return true; + } + } + + return false; +} + +bool IsContainsAccessibleItems(std::string path) { + return IsContainsAccessibleItems(PathUWP(path)); +} + +bool IsRootForAccessibleItems(PathUWP path, std::list& subRoot, bool breakOnFirstMatch = false) { + path = PathResolver(path); + + for (auto& fItem : FutureAccessItems) { + if (isChild(path.ToString(), fItem.GetPath())) { + if (breakOnFirstMatch) { + // Just checking, we don't need to loop for each item + return true; + } + auto sub = getSubRoot(path.ToString(), fItem.GetPath()); + + // This check can be better, but that's how I can do it in C++ + if (!ends_with(sub, ":")) { + bool alreadyAdded = false; + for each (auto sItem in subRoot) { + if (iequals(sItem, sub)) { + alreadyAdded = true; + break; + } + } + if (!alreadyAdded) { + subRoot.push_back(sub); + } + } + } + } + return !subRoot.empty(); +} + +bool IsRootForAccessibleItems(std::string path, std::list& subRoot, bool breakOnFirstMatch = false) { + return IsRootForAccessibleItems(PathUWP(path), subRoot, breakOnFirstMatch); +} +bool IsRootForAccessibleItems(std::string path) { + std::list tmp; + return IsRootForAccessibleItems(path, tmp, true); +} +#pragma endregion + +#pragma region Functions +bool CreateIfNotExists(int openMode) { + switch (openMode) + { + case OPEN_ALWAYS: + case CREATE_NEW: + return true; + break; + default: + return false; + } +} + +HANDLE CreateFileUWP(std::string path, int accessMode, int shareMode, int openMode) { + HANDLE handle = INVALID_HANDLE_VALUE; + if (IsValidUWP(path)) { + bool createIfNotExists = CreateIfNotExists(openMode); + auto storageItem = GetStorageItem(path, createIfNotExists); + + if (storageItem.IsValid()) { + DEBUG_LOG(FILESYS, "Getting handle (%s)", path.c_str()); + HRESULT hr = storageItem.GetHandle(&handle, accessMode, shareMode); + if (hr == E_FAIL) { + handle = INVALID_HANDLE_VALUE; + } + } + else { + handle = INVALID_HANDLE_VALUE; + DEBUG_LOG(FILESYS, "Couldn't find or access (%s)", path.c_str()); + } + } + return handle; +} + +HANDLE CreateFileUWP(std::wstring path, int accessMode, int shareMode, int openMode) { + auto pathString = convert(path); + return CreateFileUWP(pathString, accessMode, shareMode, openMode); +} + +std::map accessState; +bool CheckDriveAccess(std::string driveName, bool checkIfContainsFutureAccessItems) { + bool state = false; + + HANDLE searchResults; + WIN32_FIND_DATA findDataResult; + auto keyIter = accessState.find(driveName); + if (keyIter != accessState.end()) { + state = keyIter->second; + } + else { + try { + wchar_t* filteredPath = _wcsdup(convert(driveName)->Data()); + wcscat_s(filteredPath, sizeof(L"\\*.*"), L"\\*.*"); +#if !defined(_M_ARM) + searchResults = FindFirstFileExFromAppW( + filteredPath, FindExInfoBasic, &findDataResult, + FindExSearchNameMatch, NULL, 0); +#else + searchResults = FindFirstFileEx( + filteredPath, FindExInfoBasic, &findDataResult, + FindExSearchNameMatch, NULL, 0); +#endif + state = searchResults != NULL && searchResults != INVALID_HANDLE_VALUE; + if (state) { + FindClose(searchResults); + } + accessState.insert(std::make_pair(driveName, state)); + } + catch (...) { + } + } + + if (!state && checkIfContainsFutureAccessItems) { + // Consider the drive accessible in case it contain files/folder selected before to avoid empty results + state = IsRootForAccessibleItems(driveName) || IsContainsAccessibleItems(driveName); + } + return state; +} +bool IsValidUWP(std::string path) { + auto p = PathResolver(path); + + //Check valid path + if (p.Type() == PathTypeUWP::UNDEFINED || !p.IsAbsolute()) { + // Nothing to do here + VERBOSE_LOG(FILESYS, "File is not valid (%s)", p.ToString().c_str()); + return false; + } + + + bool state = false; + + auto resolvedPathStr = p.ToString(); + if (ends_with(resolvedPathStr, "LocalState") || ends_with(resolvedPathStr, "TempState") || ends_with(resolvedPathStr, "LocalCache")) { + state = true; + } + else + if (isChild(GetLocalFolder(), resolvedPathStr)) { + state = true; + } + else if (isChild(GetInstallationFolder(), resolvedPathStr)) { + state = true; + } + else if (isChild(GetTempFolder(), resolvedPathStr)) { + state = true; + } + + if (!state) + { + auto p = PathUWP(path); + std::string driveName = p.GetRootVolume().ToString(); + state = CheckDriveAccess(driveName, false); + } + + return !state; +} + +bool IsExistsUWP(std::string path) { + if (IsValidUWP(path)) { + auto storageItem = GetStorageItem(path); + if (storageItem.IsValid()) { + return true; + } + + // If folder is not accessible but contains accessible items + // consider it exists + if (IsContainsAccessibleItems(path)) { + return true; + } + + // If folder is not accessible but is part of accessible items + // consider it exists + std::list tmp; + if (IsRootForAccessibleItems(path, tmp, true)) { + return true; + } + } + // ERROR_LOG(FILESYS, "Couldn't find or access (%s)", path.c_str()); + return false; +} + +bool IsDirectoryUWP(std::string path) { + if (IsValidUWP(path)) { + auto storageItem = GetStorageItem(path); + if (storageItem.IsValid()) { + if (storageItem.IsDirectory()) { + return true; + } + } + } + return false; +} + +FILE* GetFileStream(std::string path, const char* mode) { + FILE* file{}; + if (IsValidUWP(path)) { + auto storageItem = GetStorageItem(path); + if (storageItem.IsValid()) { + file = storageItem.GetStream(mode); + } + else { + // Forward the request to parent folder + auto p = PathUWP(path); + auto itemName = p.GetFilename(); + auto rootPath = p.GetDirectory(); + if (IsValidUWP(rootPath)) { + storageItem = GetStorageItem(rootPath); + if (storageItem.IsValid()) { + file = storageItem.GetFileStream(itemName, mode); + } + else { + ERROR_LOG(FILESYS, "Couldn't find or access (%s)", rootPath.c_str()); + ERROR_LOG(FILESYS, "Couldn't find or access (%s)", path.c_str()); + } + } + } + } + + return file; +} + +FILE* GetFileStreamFromApp(std::string path, const char* mode) { + + FILE* file{}; + + auto pathResolved = PathUWP(ResolvePathUWP(path)); + HANDLE handle; + auto access = GENERIC_READ; + auto share = FILE_SHARE_READ; + auto creation = OPEN_EXISTING; + bool isWrite = isWriteMode(mode); + bool isAppend = isAppendMode(mode); + + if (isWrite) { + access = GENERIC_WRITE; + share = FILE_SHARE_WRITE; + creation = isAppend ? OPEN_ALWAYS : CREATE_ALWAYS; + } +#if !defined(_M_ARM) + handle = CreateFile2FromAppW(pathResolved.ToWString().c_str(), access, share, creation, nullptr); +#else + handle = CreateFile2(pathResolved.ToWString().c_str(), access, share, creation, nullptr); +#endif + if (handle != INVALID_HANDLE_VALUE) { + int flags = _O_RDONLY; + if (isWrite) { + flags = _O_RDWR; + } + file = _fdopen(_open_osfhandle((intptr_t)handle, flags), mode); + } + + return file; +} + +#pragma region Content Helpers +ItemInfoUWP GetFakeFolderInfo(std::string folder) { + ItemInfoUWP info; + auto folderPath = PathUWP(folder); + info.name = folderPath.GetFilename(); + info.fullName = folderPath.ToString(); + + info.isDirectory = true; + + info.size = 1; + info.lastAccessTime = 1000; + info.lastWriteTime = 1000; + info.changeTime = 1000; + info.creationTime = 1000; + + info.attributes = 438; + + return info; +} + +#pragma endregion + +std::list GetFolderContents(std::string path, bool deepScan) { + std::list contents; + + if (IsValidUWP(path)) { + auto storageItem = GetStorageItem(path); + if (storageItem.IsValid()) { + + // Files + // deepScan is slow, try to avoid it + auto rfiles = deepScan ? storageItem.GetAllFiles() : storageItem.GetFiles(); + for each (auto file in rfiles) { + contents.push_back(file.GetFileInfo()); + } + + // Folders + // deepScan is slow, try to avoid it + auto rfolders = deepScan ? storageItem.GetAllFolders() : storageItem.GetFolders(); + for each (auto folder in rfolders) { + contents.push_back(folder.GetFolderInfo()); + } + } + else { + DEBUG_LOG(FILESYS, "Cannot get contents!, checking for other options.. (%s)", path.c_str()); + } +; } + + if (contents.size() == 0) { + // Folder maybe not accessible or not exists + // if not accessible, maybe some items inside it were selected before + // and they already in our accessible list + if (IsContainsAccessibleItems(path)) { + DEBUG_LOG(FILESYS, "Folder contains accessible items (%s)", path.c_str()); + + // Check contents + auto cItems = GetStorageItemsByParent(path); + if (!cItems.empty()) { + for each (auto item in cItems) { + VERBOSE_LOG(FILESYS, "Appending accessible item (%s)", item.GetPath().c_str()); + contents.push_back(item.GetItemInfo()); + } + } + } + else + { + // Check if this folder is root for accessible item + // then add fake folder as sub root to avoid empty results + std::list subRoot; + if (IsRootForAccessibleItems(path, subRoot)) { + DEBUG_LOG(FILESYS, "Folder is root for accessible items (%s)", path.c_str()); + + if (!subRoot.empty()) { + for each (auto sItem in subRoot) { + VERBOSE_LOG(FILESYS, "Appending fake folder (%s)", sItem.c_str()); + contents.push_back(GetFakeFolderInfo(sItem)); + } + } + } + else { + VERBOSE_LOG(FILESYS, "Cannot get any content!.. (%s)", path.c_str()); + } + } + } + return contents; +} +std::list GetFolderContents(std::wstring path, bool deepScan) { + return GetFolderContents(convert(path), deepScan); +} + +ItemInfoUWP GetItemInfoUWP(std::string path) { + ItemInfoUWP info; + info.size = -1; + info.attributes = INVALID_FILE_ATTRIBUTES; + + if (IsValidUWP(path)) { + auto storageItem = GetStorageItem(path); + if (storageItem.IsValid()) { + info = storageItem.GetItemInfo(); + } + else { + ERROR_LOG(FILESYS, "Couldn't find or access (%s)", path.c_str()); + } + } + return info; +} +#pragma endregion + +#pragma region Basics +int64_t GetSizeUWP(std::string path) { + int64_t size = 0; + if (IsValidUWP(path)) { + auto storageItem = GetStorageItem(path); + if (storageItem.IsValid()) { + size = storageItem.GetSize(); + } + else { + ERROR_LOG(FILESYS, "Couldn't find or access (%s)", path.c_str()); + } + } + return size; +} + +bool DeleteUWP(std::string path) { + bool state = false; + if (IsValidUWP(path)) { + auto storageItem = GetStorageItem(path); + if (storageItem.IsValid()) { + DEBUG_LOG(FILESYS, "Delete (%s)", path.c_str()); + state = storageItem.Delete(); + } + else { + DEBUG_LOG(FILESYS, "Couldn't find or access (%s)", path.c_str()); + } + } + + return state; +} + +bool CreateDirectoryUWP(std::string path, bool replaceExisting) { + bool state = false; + auto p = PathUWP(path); + auto itemName = p.GetFilename(); + auto rootPath = p.GetDirectory(); + + if (IsValidUWP(rootPath)) { + auto storageItem = GetStorageItem(rootPath); + if (storageItem.IsValid()) { + DEBUG_LOG(FILESYS, "Create new folder (%s)", path.c_str()); + state = storageItem.CreateFolder(itemName, replaceExisting); + } + else { + ERROR_LOG(FILESYS, "Couldn't find or access (%s)", rootPath.c_str()); + } + } + + return state; +} + +bool CopyUWP(std::string path, std::string dest) { + bool state = false; + + if (IsValidUWP(path) && IsValidUWP(dest)) { + auto srcStorageItem = GetStorageItem(path); + if (srcStorageItem.IsValid()) { + auto destDir = dest; + auto srcName = srcStorageItem.GetName(); + auto dstPath = PathUWP(dest); + auto dstName = dstPath.GetFilename(); + // Destination must be parent folder + destDir = dstPath.GetDirectory(); + auto dstStorageItem = GetStorageItem(destDir, true, true); + if (dstStorageItem.IsValid()) { + DEBUG_LOG(FILESYS, "Copy (%s) to (%s)", path.c_str(), dest.c_str()); + state = srcStorageItem.Copy(dstStorageItem, dstName); + } + else { + ERROR_LOG(FILESYS, "Couldn't find or access (%s)", dest.c_str()); + } + } + else { + ERROR_LOG(FILESYS, "Couldn't find or access (%s)", path.c_str()); + } + } + + return state; +} + +bool MoveUWP(std::string path, std::string dest) { + bool state = false; + + if (IsValidUWP(path) && IsValidUWP(dest)) { + auto srcStorageItem = GetStorageItem(path); + + if (srcStorageItem.IsValid()) { + auto destDir = dest; + auto srcName = srcStorageItem.GetName(); + auto dstPath = PathUWP(dest); + auto dstName = dstPath.GetFilename(); + // Destination must be parent folder + destDir = dstPath.GetDirectory(); + auto dstStorageItem = GetStorageItem(destDir, true, true); + if (dstStorageItem.IsValid()) { + DEBUG_LOG(FILESYS, "Move (%s) to (%s)", path.c_str(), dest.c_str()); + state = srcStorageItem.Move(dstStorageItem, dstName); + } + else { + ERROR_LOG(FILESYS, "Couldn't find or access (%s)", dest.c_str()); + } + } + else { + ERROR_LOG(FILESYS, "Couldn't find or access (%s)", path.c_str()); + } + } + + return state; +} + +bool RenameUWP(std::string path, std::string name) { + bool state = false; + + auto srcRoot = PathUWP(path).GetDirectory(); + auto dstRoot = PathUWP(name).GetDirectory(); + // Check if system using rename to move + if (iequals(srcRoot, dstRoot)) { + auto srcStorageItem = GetStorageItem(path); + if (srcStorageItem.IsValid()) { + DEBUG_LOG(FILESYS, "Rename (%s) to (%s)", path.c_str(), name.c_str()); + state = srcStorageItem.Rename(name); + } + else { + DEBUG_LOG(FILESYS, "Couldn't find or access (%s)", path.c_str()); + } + } + else { + DEBUG_LOG(FILESYS, " Rename used as move -> call move (%s) to (%s)", path.c_str(), name.c_str()); + state = MoveUWP(path, name); + } + + return state; +} +#pragma endregion + + +#pragma region Helpers +bool OpenFile(std::string path) { + bool state = false; + + auto storageItem = GetStorageItem(path); + if (storageItem.IsValid()) { + if (!storageItem.IsDirectory()) { + ExecuteTask(state, Windows::System::Launcher::LaunchFileAsync(storageItem.GetStorageFile()), false); + } + } + else { + auto uri = ref new Windows::Foundation::Uri(convert(path)); + ExecuteTask(state, Windows::System::Launcher::LaunchUriAsync(uri), false); + } + return state; +} + +bool OpenFolder(std::string path) { + bool state = false; + auto uri = ref new Windows::Foundation::Uri(convert(path)); + + auto storageItem = GetStorageItem(path); + if (storageItem.IsValid()) { + if (storageItem.IsDirectory()) { + ExecuteTask(state, Windows::System::Launcher::LaunchFolderAsync(storageItem.GetStorageFolder()), false); + } + else { + OpenFile(storageItem.GetPath()); + } + } + return state; +} + +bool IsFirstStart() { + auto firstrun = GetDataFromLocalSettings("first_run"); + AddDataToLocalSettings("first_run", "done", true); + return firstrun.empty(); +} +#pragma endregion + +#pragma region Logs +// Get log file name +std::string currentLogFile; +std::string getLogFileName() { + //Initial new name each session/launch + if (currentLogFile.empty() || currentLogFile.size() == 0) { + std::time_t now = std::time(0); + char mbstr[100]; + std::strftime(mbstr, 100, "ppsspp %d-%m-%Y (%T).txt", std::localtime(&now)); + std::string formatedDate(mbstr); + std::replace(formatedDate.begin(), formatedDate.end(), ':', '-'); + currentLogFile = formatedDate; + } + + return currentLogFile; +} + +// Get current log file location +StorageFolder^ GetLogsStorageFolder() { + // Ensure 'LOGS' folder is created + auto workingFolder = GetStorageItem(GetWorkingFolder()); + StorageFolder^ logsFolder; + if (workingFolder.IsValid()) { + auto workingStorageFolder = workingFolder.GetStorageFolder(); + ExecuteTask(logsFolder, workingStorageFolder->CreateFolderAsync("LOGS", CreationCollisionOption::OpenIfExists)); + } + return logsFolder; +} +std::string GetLogFile() { + std::string logFilePath = ""; + + // Ensure 'LOGS' folder is created + StorageFolder^ logsFolder = GetLogsStorageFolder(); + + if (logsFolder != nullptr) { + auto logFileName = convert(getLogFileName()); + StorageFile^ logFile; + ExecuteTask(logFile, logsFolder->CreateFileAsync(logFileName, CreationCollisionOption::OpenIfExists)); + + if (logFile != nullptr) { + logFilePath = convert(logFile->Path); + } + } + + return logFilePath; +} + +// Save logs to folder selected by the user +bool SaveLogs() { + try { + auto folderPicker = ref new Windows::Storage::Pickers::FolderPicker(); + folderPicker->SuggestedStartLocation = Windows::Storage::Pickers::PickerLocationId::Desktop; + folderPicker->FileTypeFilter->Append("*"); + + StorageFolder^ saveFolder; + ExecuteTask(saveFolder, folderPicker->PickSingleFolderAsync()); + + if (saveFolder != nullptr) { + StorageFolder^ logsFolder = GetLogsStorageFolder(); + + if (logsFolder != nullptr) { + StorageFolderW logsCache(logsFolder); + logsCache.Copy(saveFolder); + } + } + } + catch (...) { + return false; + } + return true; +} + +void CleanupLogs() { + StorageFolder^ logsFolder = GetLogsStorageFolder(); + if (logsFolder != nullptr) { + StorageFolderW logsCache(logsFolder); + std::list files = logsCache.GetFiles(); + if (!files.empty()) { + for each (auto fItem in files) { + if (fItem.GetSize() == 0) { + fItem.Delete(); + } + } + } + } +} +#pragma endregion diff --git a/UWP/UWPHelpers/StorageManager.h b/UWP/UWPHelpers/StorageManager.h new file mode 100644 index 0000000000..4f3c55c888 --- /dev/null +++ b/UWP/UWPHelpers/StorageManager.h @@ -0,0 +1,106 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +// Functions: +// GetWorkingFolder() +// SetWorkingFolder(std::string location) +// GetInstallationFolder() +// GetLocalFolder() +// GetTempFolder() +// GetPicturesFolder() +// GetVideosFolder() +// GetDocumentsFolder() +// GetMusicFolder() +// GetPreviewPath(std::string path) +// +// CreateFileUWP(std::string path, int accessMode, int shareMode, int openMode) +// CreateFileUWP(std::wstring path, int accessMode, int shareMode, int openMode) +// GetFileStream(std::string path, const char* mode) +// IsValidUWP(std::string path) +// IsExistsUWP(std::string path) +// IsDirectoryUWP(std::string path) +// +// GetFolderContents(std::string path, T& files) +// GetFolderContents(std::wstring path, T& files) +// GetFileInfoUWP(std::string path, T& info) +// +// GetSizeUWP(std::string path) +// DeleteUWP(std::string path) +// CreateDirectoryUWP(std::string path, bool replaceExisting) +// RenameUWP(std::string path, std::string name) +// CopyUWP(std::string path, std::string name) +// MoveUWP(std::string path, std::string name) +// +// OpenFile(std::string path) +// OpenFolder(std::string path) +// IsFirstStart() +// +// GetLogFile() +// SaveLogs() +// CleanupLogs() + +#pragma once + +#include + +#include "StoragePath.h" +#include "StorageInfo.h" +#include "StorageAccess.h" +#include "StoragePickers.h" + +// Locations +std::string GetWorkingFolder(); // Where main data is, default is app data +void SetWorkingFolder(std::string location); // Change working location +std::string GetInstallationFolder(); +std::string GetLocalFolder(); +std::string GetTempFolder(); +std::string GetTempFile(std::string name); +std::string GetPicturesFolder(); // Requires 'picturesLibrary' capability +std::string GetVideosFolder(); // Requires 'videosLibrary' capability +std::string GetDocumentsFolder(); // Requires 'documentsLibrary' capability +std::string GetMusicFolder(); // Requires 'musicLibrary' capability +std::string GetPreviewPath(std::string path); +bool isLocalState(std::string path); + +// Management +HANDLE CreateFileUWP(std::string path, int accessMode = GENERIC_READ, int shareMode = FILE_SHARE_READ, int openMode = OPEN_EXISTING); +HANDLE CreateFileUWP(std::wstring path, int accessMode = GENERIC_READ, int shareMode = FILE_SHARE_READ, int openMode = OPEN_EXISTING); +FILE* GetFileStream(std::string path, const char* mode); +// `GetFileStreamFromApp` Will use Windows UWP API, use it instead of fopen..etc +FILE* GetFileStreamFromApp(std::string path, const char* mode); +bool IsValidUWP(std::string path); +bool IsExistsUWP(std::string path); +bool IsDirectoryUWP(std::string path); + +std::list GetFolderContents(std::string path, bool deepScan = false); +std::list GetFolderContents(std::wstring path, bool deepScan = false); +ItemInfoUWP GetItemInfoUWP(std::string path); +bool IsContainsAccessibleItems(std::string path); +bool IsRootForAccessibleItems(std::string path); +// 'checkIfContainsFutureAccessItems' for listing purposes not real access, 'driveName' like C: +bool CheckDriveAccess(std::string driveName, bool checkIfContainsFutureAccessItems); + +// Basics +int64_t GetSizeUWP(std::string path); +bool DeleteUWP(std::string path); +bool CreateDirectoryUWP(std::string path, bool replaceExisting = true); +bool RenameUWP(std::string path, std::string name); +// Add file name to destination path +bool CopyUWP(std::string path, std::string dest); +// Add file name to destination path +bool MoveUWP(std::string path, std::string dest); + +// Helpers +bool OpenFile(std::string path); +bool OpenFolder(std::string path); +bool IsFirstStart(); +std::string ResolvePathUWP(std::string path); + +// Log helpers +std::string GetLogFile(); +bool SaveLogs(); // With picker +void CleanupLogs(); + diff --git a/UWP/UWPHelpers/StoragePath.cpp b/UWP/UWPHelpers/StoragePath.cpp new file mode 100644 index 0000000000..77f9189225 --- /dev/null +++ b/UWP/UWPHelpers/StoragePath.cpp @@ -0,0 +1,272 @@ +// UWP STORAGE MANAGER +// Based on 'Path' from PPSSPP +// For updates check: https://github.com/basharast/UWP2Win32 + +#include +#include +#include + +#include "StoragePath.h" +#include "StorageLog.h" +#include "StorageExtensions.h" + +PathUWP::PathUWP(const std::string &str) { + Init(str); +} + +PathUWP::PathUWP(const std::wstring &str) { + type_ = PathTypeUWP::NATIVE; + Init(convert(str)); +} + +void PathUWP::Init(const std::string &str) { + if (str.empty()) { + type_ = PathTypeUWP::UNDEFINED; + path_.clear(); + } else if (starts_with(str, "http://") || starts_with(str, "https://")) { + type_ = PathTypeUWP::HTTP; + path_ = str; + } else { + type_ = PathTypeUWP::NATIVE; + path_ = str; + } + + // Flip all the slashes around. We flip them back on ToWString(). + for (size_t i = 0; i < path_.size(); i++) { + if (path_[i] == '\\') { + path_[i] = '/'; + } + } + + // Don't pop_back if it's just "/". + if (type_ == PathTypeUWP::NATIVE && path_.size() > 1 && path_.back() == '/') { + path_.pop_back(); + } +} + +// We always use forward slashes internally, we convert to backslash only when +// converted to a wstring. +PathUWP PathUWP::operator /(const std::string &subdir) const { + // Direct string manipulation. + + if (subdir.empty()) { + return PathUWP(path_); + } + std::string fullPath = path_; + if (subdir.front() != '/' && (fullPath.empty() || fullPath.back() != '/')) { + fullPath += "/"; + } + fullPath += subdir; + // Prevent adding extra slashes. + if (fullPath.back() == '/') { + fullPath.pop_back(); + } + return PathUWP(fullPath); +} + +void PathUWP::operator /=(const std::string &subdir) { + *this = *this / subdir; +} + +PathUWP PathUWP::WithExtraExtension(const std::string &ext) const { + return PathUWP(path_ + ext); +} + +PathUWP PathUWP::WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const { + if (ends_with(path_, oldExtension)) { + std::string newPath = path_.substr(0, path_.size() - oldExtension.size()); + return PathUWP(newPath + newExtension); + } else { + return PathUWP(*this); + } +} + +PathUWP PathUWP::WithReplacedExtension(const std::string &newExtension) const { + if (path_.empty()) { + return PathUWP(*this); + } + std::string extension = GetFileExtension(); + std::string newPath = path_.substr(0, path_.size() - extension.size()) + newExtension; + return PathUWP(newPath); +} + +std::string PathUWP::GetFilename() const { + size_t pos = path_.rfind('/'); + if (pos != std::string::npos) { + return path_.substr(pos + 1); + } + return path_; +} + +static std::string GetExtFromString(const std::string &str) { + size_t pos = str.rfind("."); + if (pos == std::string::npos) { + return ""; + } + size_t slash_pos = str.rfind("/"); + if (slash_pos != std::string::npos && slash_pos > pos) { + // Don't want to detect "df/file" from "/as.df/file" + return ""; + } + std::string ext = str.substr(pos); + for (size_t i = 0; i < ext.size(); i++) { + ext[i] = tolower(ext[i]); + } + return ext; +} + +std::string PathUWP::GetFileExtension() const { + return GetExtFromString(path_); +} + +std::string PathUWP::GetDirectory() const { + size_t pos = path_.rfind('/'); + if (type_ == PathTypeUWP::HTTP) { + // Things are a bit different for HTTP, because we probably ended with /. + if (pos + 1 == path_.size()) { + pos = path_.rfind('/', pos - 1); + if (pos != path_.npos && pos > 8) { + return path_.substr(0, pos + 1); + } + } + } + + if (pos != std::string::npos) { + if (pos == 0) { + return "/"; // We're at the root. + } + return path_.substr(0, pos); + } else if (path_.size() == 2 && path_[1] == ':') { + // Windows fake-root. + return "/"; + } else { + // There could be a ':', too. Unlike the slash, let's include that + // in the returned directory. + size_t c_pos = path_.rfind(':'); + if (c_pos != std::string::npos) { + return path_.substr(0, c_pos + 1); + } + } + // No directory components, we're a relative path. + return path_; +} + +bool PathUWP::FilePathContainsNoCase(const std::string &needle) const { + std::string haystack; + + haystack = path_; + auto pred = [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }; + auto found = std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), pred); + return found != haystack.end(); +} + +bool PathUWP::StartsWith(const PathUWP &other) const { + if (type_ != other.type_) { + // Bad + return false; + } + return starts_with(path_, other.path_); +} + +const std::string &PathUWP::ToString() const { + return path_; +} + +std::wstring PathUWP::ToWString() const { + std::wstring w = convertToWString(path_); + for (size_t i = 0; i < w.size(); i++) { + if (w[i] == '/') { + w[i] = '\\'; + } + } + return w; +} + +std::string PathUWP::ToVisualString() const { + if (type_ == PathTypeUWP::NATIVE) { + return replace2(path_, "/", "\\"); + } else { + return path_; + } +} + +bool PathUWP::CanNavigateUp() const { + if (path_ == "/" || path_.empty()) { + return false; + } + if (type_ == PathTypeUWP::HTTP) { + size_t rootSlash = path_.find_first_of('/', strlen("https://")); + if (rootSlash == path_.npos || path_.size() < rootSlash + 1) { + // This means, "http://server" or "http://server/". Can't go up. + return false; + } + } + return true; +} + +PathUWP PathUWP::NavigateUp() const { + std::string dir = GetDirectory(); + return PathUWP(dir); +} + +PathUWP PathUWP::GetRootVolume() const { + if (!IsAbsolute()) { + // Can't do anything + return PathUWP(path_); + } + + if (path_[1] == ':') { + // Windows path with drive letter + std::string path = path_.substr(0, 2); + return PathUWP(path); + } + // Support UNC and device paths. + if (path_[0] == '/' && path_[1] == '/') { + size_t next = 2; + if ((path_[2] == '.' || path_[2] == '?') && path_[3] == '/') { + // Device path, or "\\.\UNC" path, skip the dot and consider the device the root. + next = 4; + } + + size_t len = path_.find_first_of('/', next); + return PathUWP(path_.substr(0, len)); + } + return PathUWP("/"); +} + +bool PathUWP::IsAbsolute() const { + if (path_.empty()) + return true; + else if (path_.front() == '/') + return true; + else if (path_.size() > 3 && path_[1] == ':') + return true; // Windows path with drive letter + else + return false; +} + +bool PathUWP::ComputePathTo(const PathUWP &other, std::string &path) const { + if (other == *this) { + path.clear(); + return true; + } + + if (!other.StartsWith(*this)) { + // Can't do this. Should return an error. + return false; + } + + if (*this == other) { + // Equal, the path is empty. + path.clear(); + return true; + } + + if (path_ == "/") { + path = other.path_.substr(1); + return true; + } else { + path = other.path_.substr(path_.size() + 1); + return true; + } +} diff --git a/UWP/UWPHelpers/StoragePath.h b/UWP/UWPHelpers/StoragePath.h new file mode 100644 index 0000000000..94d6e79405 --- /dev/null +++ b/UWP/UWPHelpers/StoragePath.h @@ -0,0 +1,113 @@ +// UWP STORAGE MANAGER +// Based on 'Path' from PPSSPP +// For updates check: https://github.com/basharast/UWP2Win32 + +#pragma once + +#include + +#define HOST_IS_CASE_SENSITIVE 0 + +enum class PathTypeUWP { + UNDEFINED = 0, + NATIVE = 1, // Can be relative. + CONTENT_URI = 2, // Android only. Can only be absolute! + HTTP = 3, // http://, https:// +}; + +// Windows paths are always stored with '/' slashes in a Path. +// On .ToWString(), they are flipped back to '\'. + +class PathUWP { +private: + void Init(const std::string &str); + +public: + PathUWP() : type_(PathTypeUWP::UNDEFINED) {} + explicit PathUWP(const std::string &str); + + explicit PathUWP(const std::wstring &str); + + PathTypeUWP Type() const { + return type_; + } + + bool Valid() const { return !path_.empty(); } + bool IsRoot() const { return path_ == "/"; } // Special value - only path that can end in a slash. + + // Some std::string emulation for simplicity. + bool empty() const { return !Valid(); } + void clear() { + type_ = PathTypeUWP::UNDEFINED; + path_.clear(); + } + size_t size() const { + return path_.size(); + } + + // WARNING: Potentially unsafe usage, if it's not NATIVE. + const char *c_str() const { + return path_.c_str(); + } + + bool IsAbsolute() const; + + // Returns a path extended with a subdirectory. + PathUWP operator /(const std::string &subdir) const; + + // Navigates down into a subdir. + void operator /=(const std::string &subdir); + + // File extension manipulation. + PathUWP WithExtraExtension(const std::string &ext) const; + PathUWP WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const; + PathUWP WithReplacedExtension(const std::string &newExtension) const; + + // Removes the last component. + std::string GetFilename() const; // Really, GetLastComponent. Could be a file or directory. Includes the extension. + std::string GetFileExtension() const; // Always lowercase return. Includes the dot. + std::string GetDirectory() const; + + const std::string &ToString() const; + + std::wstring ToWString() const; + + std::string ToVisualString() const; + + bool CanNavigateUp() const; + PathUWP NavigateUp() const; + + // Navigates as far up as possible from this path. If not possible to navigate upwards, returns the same path. + // Not actually always the root of the volume, especially on systems like Mac and Linux where things are often mounted. + // For Android directory trees, navigates to the root of the tree. + PathUWP GetRootVolume() const; + + bool ComputePathTo(const PathUWP&other, std::string &path) const; + + bool operator ==(const PathUWP&other) const { + return path_ == other.path_ && type_ == other.type_; + } + bool operator !=(const PathUWP&other) const { + return path_ != other.path_ || type_ != other.type_; + } + + bool FilePathContainsNoCase(const std::string &needle) const; + + bool StartsWith(const PathUWP&other) const; + + bool operator <(const PathUWP&other) const { + return path_ < other.path_; + } + bool operator >(const PathUWP&other) const { + return path_ > other.path_; + } + +private: + // The internal representation is currently always the plain string. + // For CPU efficiency we could keep an AndroidStorageContentURI too, + // but I don't think the encode/decode cost is significant. We simply create + // those for processing instead. + std::string path_; + + PathTypeUWP type_; +}; diff --git a/UWP/UWPHelpers/StoragePickers.cpp b/UWP/UWPHelpers/StoragePickers.cpp new file mode 100644 index 0000000000..4795e7e0f2 --- /dev/null +++ b/UWP/UWPHelpers/StoragePickers.cpp @@ -0,0 +1,80 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan + +// Functions: +// ChooseFolder() +// ChooseFile(std::vector exts) + +#include "pch.h" + +#include "StorageLog.h" +#include "StorageExtensions.h" +#include "StorageAsync.h" +#include "StorageAccess.h" + +using namespace Platform; +using namespace Windows::Storage; +using namespace Windows::Foundation; + +extern void AddItemToFutureList(IStorageItem^ folder); + +// Call folder picker (the selected folder will be added to future list) +concurrency::task PickSingleFolder() +{ + auto folderPicker = ref new Windows::Storage::Pickers::FolderPicker(); + folderPicker->SuggestedStartLocation = Windows::Storage::Pickers::PickerLocationId::Desktop; + folderPicker->FileTypeFilter->Append("*"); + + return concurrency::create_task(folderPicker->PickSingleFolderAsync()).then([](StorageFolder^ folder) { + auto path = ref new Platform::String(); + if (folder != nullptr) + { + AddItemToFutureList(folder); + path = folder->Path; + } + return path; + }); +} + +// Call file picker (the selected file will be added to future list) +concurrency::task PickSingleFile(std::vector exts) +{ + auto filePicker = ref new Windows::Storage::Pickers::FileOpenPicker(); + filePicker->SuggestedStartLocation = Windows::Storage::Pickers::PickerLocationId::Desktop; + filePicker->ViewMode = Pickers::PickerViewMode::List; + + if (exts.size() > 0) { + for each (auto ext in exts) { + filePicker->FileTypeFilter->Append(convert(ext)); + } + } + else + { + filePicker->FileTypeFilter->Append("*"); + } + return concurrency::create_task(filePicker->PickSingleFileAsync()).then([](StorageFile^ file) { + auto path = ref new Platform::String(); + if (file != nullptr) + { + AddItemToFutureList(file); + path = file->Path; + } + return path; + }); +} + + +concurrency::task ChooseFile(std::vector exts) { + return PickSingleFile(exts).then([](Platform::String^ filePath) { + return convert(filePath); + }); +} + +concurrency::task ChooseFolder() { + return PickSingleFolder().then([](Platform::String^ folderPath) { + return convert(folderPath); + }); + +} diff --git a/UWP/UWPHelpers/StoragePickers.h b/UWP/UWPHelpers/StoragePickers.h new file mode 100644 index 0000000000..601a942510 --- /dev/null +++ b/UWP/UWPHelpers/StoragePickers.h @@ -0,0 +1,18 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan + +// Functions: +// ChooseFolder() +// ChooseFile(std::vector exts) + +#pragma once + +#include +#include +#include +#include + +concurrency::task ChooseFolder(); +concurrency::task ChooseFile(std::vector exts); diff --git a/UWP/UWPHelpers/UWP2C.cpp b/UWP/UWPHelpers/UWP2C.cpp new file mode 100644 index 0000000000..bdddac39be --- /dev/null +++ b/UWP/UWPHelpers/UWP2C.cpp @@ -0,0 +1,93 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +// This is small bridge to invoke UWP Storage manager + +// Functions: +// CreateFileUWP(const char* path, int accessMode, int shareMode, int openMode) +// GetFileAttributesUWP(const void* name, void* lpFileInformation) +// DeleteFileUWP(const void* name) + +#include "UWP2C.h" +#include "StorageManager.h" +#include "StorageExtensions.h" + +#ifdef __cplusplus +extern "C" { +#endif +void* CreateFileUWP(const char* path, int accessMode, int shareMode, int openMode) { + std::string fn = convert(path); + + return (void*)CreateFileUWP(fn, accessMode, shareMode, openMode); +} + +int GetFileAttributesUWP(const void* name, void* lpFileInformation) { + size_t size = sizeof(WIN32_FILE_ATTRIBUTE_DATA); + WIN32_FILE_ATTRIBUTE_DATA* file_attributes = (WIN32_FILE_ATTRIBUTE_DATA*)(malloc(size)); + + std::string fn = convert((const char*)name); + HANDLE handle = CreateFileUWP(fn); + + FILETIME createTime{}; + FILETIME changeTime{}; + + DWORD fileSizeHigh = 0; + DWORD fileSizeLow = 0; + DWORD fileAttributes = 32; + + if (handle != INVALID_HANDLE_VALUE) { + size_t size = sizeof(FILE_BASIC_INFO); + FILE_BASIC_INFO* information = (FILE_BASIC_INFO*)(malloc(size)); + if (information) { + if (FALSE != GetFileInformationByHandleEx(handle, FileBasicInfo, information, (DWORD)size)) { + createTime.dwHighDateTime = information->CreationTime.HighPart; + createTime.dwLowDateTime = information->CreationTime.LowPart; + changeTime.dwHighDateTime = information->ChangeTime.HighPart; + changeTime.dwLowDateTime = information->ChangeTime.LowPart; + fileAttributes = information->FileAttributes; + } + } + LARGE_INTEGER fsize{ 0 }; + if (FALSE == GetFileSizeEx(handle, &fsize)) { + LARGE_INTEGER end_offset; + const LARGE_INTEGER zero{}; + if (SetFilePointerEx(handle, zero, &end_offset, FILE_END) == 0) { + CloseHandle(handle); + } + else { + fileSizeHigh = (DWORD)end_offset.HighPart; + fileSizeLow = (DWORD)end_offset.LowPart; + SetFilePointerEx(handle, zero, nullptr, FILE_BEGIN); + CloseHandle(handle); + } + } + else { + fileSizeHigh = (DWORD)fsize.HighPart; + fileSizeLow = (DWORD)fsize.LowPart; + + CloseHandle(handle); + } + } + + ((WIN32_FILE_ATTRIBUTE_DATA*)lpFileInformation)->ftCreationTime = createTime; + ((WIN32_FILE_ATTRIBUTE_DATA*)lpFileInformation)->ftLastAccessTime = changeTime; + ((WIN32_FILE_ATTRIBUTE_DATA*)lpFileInformation)->ftLastWriteTime = changeTime; + ((WIN32_FILE_ATTRIBUTE_DATA*)lpFileInformation)->nFileSizeHigh = fileSizeHigh; + ((WIN32_FILE_ATTRIBUTE_DATA*)lpFileInformation)->nFileSizeLow = fileSizeLow; + ((WIN32_FILE_ATTRIBUTE_DATA*)lpFileInformation)->dwFileAttributes = fileAttributes; + + return 1; +} + +int DeleteFileUWP(const void* name) { + std::string fn = convert((const char*)name); + bool state = DeleteUWP(fn); + + return state ? 1 : 0; +} +#ifdef __cplusplus +} +#endif diff --git a/UWP/UWPHelpers/UWP2C.h b/UWP/UWPHelpers/UWP2C.h new file mode 100644 index 0000000000..baa56f710d --- /dev/null +++ b/UWP/UWPHelpers/UWP2C.h @@ -0,0 +1,26 @@ +// UWP STORAGE MANAGER +// Copyright (c) 2023 Bashar Astifan. +// Email: bashar@astifan.online +// Telegram: @basharastifan +// GitHub: https://github.com/basharast/UWP2Win32 + +// This is small bridge to invoke UWP Storage manager + +// Functions: +// CreateFileUWP(const char* path, int accessMode, int shareMode, int openMode) +// GetFileAttributesUWP(const void* name, void* lpFileInformation) +// DeleteFileUWP(const void* name) + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void* CreateFileUWP(const char* path, int accessMode, int shareMode, int openMode); +int GetFileAttributesUWP(const void* name, void* lpFileInformation); +int DeleteFileUWP(const void* name); + +#ifdef __cplusplus +} +#endif diff --git a/ext/libzip/zip_source_file_win32_utf16.c b/ext/libzip/zip_source_file_win32_utf16.c index 4b32ac3a74..7e0e377852 100644 --- a/ext/libzip/zip_source_file_win32_utf16.c +++ b/ext/libzip/zip_source_file_win32_utf16.c @@ -32,6 +32,9 @@ */ #ifdef MS_UWP #include +#if !defined(__LIBRETRO__) +#include "UWP/UWPHelpers/UWP2C.h" +#endif #endif #include "zip_source_file_win32.h" @@ -41,6 +44,36 @@ static HANDLE __stdcall utf16_create_file(const char *name, DWORD access, DWORD static void utf16_make_tempname(char *buf, size_t len, const char *name, zip_uint32_t i); static char *utf16_strdup(const char *string); +#ifdef MS_UWP && !defined(__LIBRETRO__) +static BOOL __stdcall GetFileAttr(const void* name, GET_FILEEX_INFO_LEVELS info_level, void* lpFileInformation) { + BOOL state = GetFileAttributesExFromAppW(name, info_level, lpFileInformation); + if (state == FALSE) { + // Ignore `info_level` not in use for now + state = GetFileAttributesUWP(name, lpFileInformation); + } + return state; +} + +static BOOL __stdcall DelFile(const void* name) { + BOOL state = DeleteFileFromAppW(name); + if (state == FALSE) { + state = DeleteFileUWP(name); + } + return state; +} + +zip_win32_file_operations_t ops_utf16 = { + utf16_allocate_tempname, + utf16_create_file, // Will invoke UWP Storage manager (If needed) + DelFile, // Will invoke UWP Storage manager + GetFileAttributesW, + GetFileAttr, // Will invoke UWP Storage manager + utf16_make_tempname, + MoveFileExW, + SetFileAttributesW, + utf16_strdup +}; +#else zip_win32_file_operations_t ops_utf16 = { utf16_allocate_tempname, utf16_create_file, @@ -52,6 +85,7 @@ zip_win32_file_operations_t ops_utf16 = { SetFileAttributesW, utf16_strdup }; +#endif ZIP_EXTERN zip_source_t * zip_source_win32w(zip_t *za, const wchar_t *fname, zip_uint64_t start, zip_int64_t len) { @@ -95,7 +129,13 @@ utf16_create_file(const char *name, DWORD access, DWORD share_mode, PSECURITY_AT return CreateFile2((const wchar_t *)name, access, share_mode, creation_disposition, &extParams); #else - return CreateFile2FromAppW((const wchar_t *)name, access, share_mode, creation_disposition, NULL); + HANDLE h = CreateFile2FromAppW((const wchar_t *)name, access, share_mode, creation_disposition, NULL); +#if !defined(__LIBRETRO__) + if (h == INVALID_HANDLE_VALUE) { + h = CreateFileUWP(name, (int)access, (int)share_mode, (int)creation_disposition); + } +#endif + return h; #endif #else return CreateFileW((const wchar_t *)name, access, share_mode, security_attributes, creation_disposition, file_attributes, template_file);