1 /* Copyright (C) 2018-2020 The RetroArch team
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (vfs_implementation_uwp.cpp).
5 * ---------------------------------------------------------------------------------------
7 * Permission is hereby granted, free of charge,
8 * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation the rights to
10 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 #include <retro_environment.h>
29 #include <wrl/implements.h>
31 #include <collection.h>
33 #include <fileapifromapp.h>
46 #include <vfs/vfs_implementation.h>
48 #include <encodings/utf.h>
49 #include <retro_miscellaneous.h>
50 #include <file/file_path.h>
51 #include <string/stdstring.h>
52 #include <retro_environment.h>
53 #include <uwp/uwp_async.h>
54 #include <uwp/std_filesystem_compat.h>
58 /* UWP deals with paths containing / instead of
59 * \ way worse than normal Windows */
60 /* and RetroArch may sometimes mix them
61 * (e.g. on archive extraction) */
62 static void windowsize_path(wchar_t* path)
77 struct retro_vfs_file_handle
79 struct libretro_vfs_implementation_file
92 enum vfs_scheme scheme;
95 #define RFILE_HINT_UNBUFFERED (1 << 8)
97 int retro_vfs_file_close_impl(libretro_vfs_implementation_file* stream)
105 if (stream->buf != NULL)
110 if (stream->orig_path)
111 free(stream->orig_path);
118 int retro_vfs_file_error_impl(libretro_vfs_implementation_file* stream)
120 return ferror(stream->fp);
123 int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file* stream)
130 int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file* stream, int64_t length)
132 if (stream && _chsize(_fileno(stream->fp), length) == 0)
137 int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file* stream)
142 if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
143 return _ftelli64(stream->fp);
144 if (lseek(stream->fd, 0, SEEK_CUR) < 0)
150 int64_t retro_vfs_file_seek_internal(
151 libretro_vfs_implementation_file* stream,
152 int64_t offset, int whence)
157 if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
158 return _fseeki64(stream->fp, offset, whence);
159 if (lseek(stream->fd, (off_t)offset, whence) < 0)
165 int64_t retro_vfs_file_seek_impl(libretro_vfs_implementation_file* stream,
166 int64_t offset, int seek_position)
168 return retro_vfs_file_seek_internal(stream, offset, seek_position);
171 int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file* stream,
172 void* s, uint64_t len)
174 if (!stream || (!stream->fp && stream->fh == INVALID_HANDLE_VALUE) || !s)
177 if (stream->fh != INVALID_HANDLE_VALUE)
180 ReadFile(stream->fh, (char*)s, len, &_bytes_read, NULL);
181 return (int64_t)_bytes_read;
184 if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
185 return fread(s, 1, (size_t)len, stream->fp);
186 return read(stream->fd, s, (size_t)len);
190 int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file* stream, const void* s, uint64_t len)
192 if (!stream || (!stream->fp && stream->fh == INVALID_HANDLE_VALUE) || !s)
195 if (stream->fh != INVALID_HANDLE_VALUE)
198 WriteFile(stream->fh, s, len, &bytes_written, NULL);
199 return (int64_t)bytes_written;
202 if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
203 return fwrite(s, 1, (size_t)len, stream->fp);
205 return write(stream->fd, s, (size_t)len);
208 int retro_vfs_file_flush_impl(libretro_vfs_implementation_file* stream)
210 if (stream && fflush(stream->fp) == 0)
215 int retro_vfs_file_remove_impl(const char *path)
223 path_wide = utf8_to_utf16_string_alloc(path);
224 windowsize_path(path_wide);
226 /* Try Win32 first, this should work in AppData */
227 result = DeleteFileFromAppW(path_wide);
235 libretro_vfs_implementation_file* retro_vfs_file_open_impl(
236 const char* path, unsigned mode, unsigned hints)
239 std::wstring path_wstring;
241 DWORD creationDisposition;
242 wchar_t *path_wide = NULL;
244 const char *mode_str = NULL;
245 libretro_vfs_implementation_file* stream =
246 (libretro_vfs_implementation_file*)
247 malloc(sizeof(*stream));
253 stream->hints = hints;
258 stream->orig_path = NULL;
261 stream->mapped = NULL;
262 stream->scheme = VFS_SCHEME_NONE;
276 path += sizeof("vfsonly://")-1;
279 path_wide = utf8_to_utf16_string_alloc(path);
280 windowsize_path(path_wide);
281 path_wstring = path_wide;
286 size_t p = path_wstring.find(L"\\\\");
287 if (p == std::wstring::npos)
289 path_wstring.replace(p, 2, L"\\");
292 path_wstring = L"\\\\?\\" + path_wstring;
293 stream->orig_path = strdup(path);
295 stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS;
299 case RETRO_VFS_FILE_ACCESS_READ:
301 flags = O_RDONLY | O_BINARY;
304 case RETRO_VFS_FILE_ACCESS_WRITE:
306 flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
309 case RETRO_VFS_FILE_ACCESS_READ_WRITE:
311 flags = O_RDWR | O_CREAT | O_TRUNC | O_BINARY;
314 case RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING:
315 case RETRO_VFS_FILE_ACCESS_READ_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING:
317 flags = O_RDWR | O_BINARY;
326 case RETRO_VFS_FILE_ACCESS_READ_WRITE:
327 desireAccess = GENERIC_READ | GENERIC_WRITE;
329 case RETRO_VFS_FILE_ACCESS_WRITE:
330 desireAccess = GENERIC_WRITE;
332 case RETRO_VFS_FILE_ACCESS_READ:
333 desireAccess = GENERIC_READ;
336 if (mode == RETRO_VFS_FILE_ACCESS_READ)
337 creationDisposition = OPEN_EXISTING;
339 creationDisposition = (mode & RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING) != 0
343 if ((file_handle = CreateFile2FromAppW(path_wstring.data(), desireAccess,
344 FILE_SHARE_READ, creationDisposition, NULL)) == INVALID_HANDLE_VALUE)
347 stream->fh = file_handle;
348 if ((stream->fd = _open_osfhandle((uint64)stream->fh, flags)) == -1)
352 FILE *fp = _fdopen(stream->fd, mode_str);
359 /* Regarding setvbuf:
361 * https://www.freebsd.org/cgi/man.cgi?query=setvbuf&apropos=0&sektion=0&manpath=FreeBSD+11.1-RELEASE&arch=default&format=html
363 * If the size argument is not zero but buf is NULL,
364 * a buffer of the given size will be allocated immediately, and
365 * released on close. This is an extension to ANSI C.
367 * Since C89 does not support specifying a NULL buffer
368 * with a non-zero size, we create and track our own buffer for it.
370 /* TODO: this is only useful for a few platforms,
371 * find which and add ifdef */
372 if (stream->scheme != VFS_SCHEME_CDROM)
374 stream->buf = (char*)calloc(1, 0x4000);
376 setvbuf(stream->fp, stream->buf, _IOFBF, 0x4000);
379 retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
380 retro_vfs_file_seek_internal(stream, 0, SEEK_END);
382 stream->size = retro_vfs_file_tell_impl(stream);
384 retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
389 retro_vfs_file_close_impl(stream);
393 static int uwp_mkdir_impl(std::experimental::filesystem::path dir)
395 /*I feel like this should create the directory recursively but the existing implementation does not so this update won't
396 *I put in the work but I just commented out the stuff you would need */
397 WIN32_FILE_ATTRIBUTE_DATA lpFileInfo;
398 bool parent_dir_exists = false;
403 /* Check if file attributes can be gotten successfully */
404 if (GetFileAttributesExFromAppW(dir.parent_path().wstring().c_str(), GetFileExInfoStandard, &lpFileInfo))
406 /* Check that the files attributes are not null or empty */
407 if (lpFileInfo.dwFileAttributes != INVALID_FILE_ATTRIBUTES && lpFileInfo.dwFileAttributes != 0)
409 if (lpFileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
410 parent_dir_exists = true;
413 if (!parent_dir_exists)
415 /* Try to create parent dir */
416 int success = uwp_mkdir_impl(dir.parent_path());
417 if (success != 0 && success != -2)
422 /* Try Win32 first, this should work in AppData */
423 if (CreateDirectoryFromAppW(dir.wstring().c_str(), NULL))
426 if (GetLastError() == ERROR_ALREADY_EXISTS)
432 int retro_vfs_mkdir_impl(const char* dir)
434 return uwp_mkdir_impl(std::filesystem::path(dir));
437 /* The first run paramater is used to avoid error checking
438 * when doing recursion.
439 * Unlike the initial implementation, this can move folders
440 * even empty ones when you want to move a directory structure.
442 * This will fail even if a single file cannot be moved.
444 static int uwp_move_path(
445 std::filesystem::path old_path,
446 std::filesystem::path new_path,
449 if (old_path.empty() || new_path.empty())
454 WIN32_FILE_ATTRIBUTE_DATA lpFileInfo, targetfileinfo;
455 bool parent_dir_exists = false;
457 /* Make sure that parent path exists */
458 if (GetFileAttributesExFromAppW(
459 new_path.parent_path().wstring().c_str(),
460 GetFileExInfoStandard, &lpFileInfo))
462 /* Check that the files attributes are not null or empty */
463 if ( lpFileInfo.dwFileAttributes
464 != INVALID_FILE_ATTRIBUTES
465 && lpFileInfo.dwFileAttributes != 0)
467 /* Parent path doesn't exist, so we gotta create it */
468 if (!(lpFileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
469 uwp_mkdir_impl(new_path.parent_path());
473 /* Make sure that source path exists */
474 if (GetFileAttributesExFromAppW(old_path.wstring().c_str(), GetFileExInfoStandard, &lpFileInfo))
476 /* Check that the files attributes are not null or empty */
477 if ( lpFileInfo.dwFileAttributes != INVALID_FILE_ATTRIBUTES
478 && lpFileInfo.dwFileAttributes != 0)
480 /* Check if source path is a dir */
481 if (lpFileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
484 /* create the target dir */
485 CreateDirectoryFromAppW(new_path.wstring().c_str(), NULL);
486 /* Call move function again but with first run disabled in
487 * order to move the folder */
488 if ((result = uwp_move_path(old_path, new_path, false)) != 0)
493 /* The file that we want to move exists so we can copy it now
494 * check if target file already exists. */
495 if (GetFileAttributesExFromAppW(
496 new_path.wstring().c_str(),
497 GetFileExInfoStandard,
501 targetfileinfo.dwFileAttributes != INVALID_FILE_ATTRIBUTES
502 && targetfileinfo.dwFileAttributes != 0
503 && (!(targetfileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)))
505 if (DeleteFileFromAppW(new_path.wstring().c_str()))
510 if (!MoveFileFromAppW(old_path.wstring().c_str(),
511 new_path.wstring().c_str()))
514 uwp_set_acl(new_path.wstring().c_str(), L"S-1-15-2-1");
522 HANDLE searchResults;
523 WIN32_FIND_DATA findDataResult;
524 /* We are bypassing error checking and moving a dir.
525 * First we have to get a list of files in the dir. */
526 wchar_t* filteredPath = wcsdup(old_path.wstring().c_str());
527 wcscat_s(filteredPath, sizeof(L"\\*.*"), L"\\*.*");
528 searchResults = FindFirstFileExFromAppW(
529 filteredPath, FindExInfoBasic, &findDataResult,
530 FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH);
532 if (searchResults != INVALID_HANDLE_VALUE)
537 if ( wcscmp(findDataResult.cFileName, L".") != 0
538 && wcscmp(findDataResult.cFileName, L"..") != 0)
540 std::filesystem::path temp_old = old_path;
541 std::filesystem::path temp_new = new_path;
542 temp_old /= findDataResult.cFileName;
543 temp_new /= findDataResult.cFileName;
544 if ( findDataResult.dwFileAttributes
545 & FILE_ATTRIBUTE_DIRECTORY)
547 CreateDirectoryFromAppW(temp_new.wstring().c_str(), NULL);
548 if (uwp_move_path(temp_old, temp_new, false) != 0)
553 WIN32_FILE_ATTRIBUTE_DATA targetfileinfo;
554 /* The file that we want to move exists so we can copy
556 * Check if target file already exists. */
557 if (GetFileAttributesExFromAppW(temp_new.wstring().c_str(),
558 GetFileExInfoStandard, &targetfileinfo))
561 (targetfileinfo.dwFileAttributes !=
562 INVALID_FILE_ATTRIBUTES)
563 && (targetfileinfo.dwFileAttributes != 0)
564 && (!(targetfileinfo.dwFileAttributes
565 & FILE_ATTRIBUTE_DIRECTORY)))
567 if (DeleteFileFromAppW(temp_new.wstring().c_str()))
572 if (!MoveFileFromAppW(temp_old.wstring().c_str(),
573 temp_new.wstring().c_str()))
575 /* Set ACL - this step sucks or at least used to
576 * before I made a whole function
577 * Don't know if we actually "need" to set the ACL
579 uwp_set_acl(temp_new.wstring().c_str(), L"S-1-15-2-1");
582 } while (FindNextFile(searchResults, &findDataResult));
583 FindClose(searchResults);
592 /* C doesn't support default arguments so we wrap it up in a shell to enable
593 * us to use default arguments.
594 * Default arguments mean that we can do better recursion */
595 int retro_vfs_file_rename_impl(const char* old_path, const char* new_path)
597 return uwp_move_path(std::filesystem::path(old_path),
598 std::filesystem::path(old_path), true);
601 const char *retro_vfs_file_get_path_impl(libretro_vfs_implementation_file *stream)
603 /* Should never happen, do something noisy so caller can be fixed */
606 return stream->orig_path;
609 int retro_vfs_stat_impl(const char *path, int32_t *size)
612 _WIN32_FILE_ATTRIBUTE_DATA attribdata;
617 path_wide = utf8_to_utf16_string_alloc(path);
618 windowsize_path(path_wide);
620 /* Try Win32 first, this should work in AppData */
621 if (GetFileAttributesExFromAppW(path_wide,
622 GetFileExInfoStandard, &attribdata))
624 if (attribdata.dwFileAttributes != INVALID_FILE_ATTRIBUTES)
626 if (!(attribdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
631 sz.HighPart = attribdata.nFileSizeHigh;
632 sz.LowPart = attribdata.nFileSizeLow;
637 return (attribdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
638 ? RETRO_VFS_STAT_IS_VALID | RETRO_VFS_STAT_IS_DIRECTORY
639 : RETRO_VFS_STAT_IS_VALID;
647 struct retro_vfs_dir_handle
649 struct libretro_vfs_implementation_dir
653 WIN32_FIND_DATAW entry;
656 char path[PATH_MAX_LENGTH];
659 libretro_vfs_implementation_dir* retro_vfs_opendir_impl(
660 const char* name, bool include_hidden)
664 wchar_t* path_wide = NULL;
665 libretro_vfs_implementation_dir* rdir;
667 /* Reject NULL or empty string paths*/
668 if (!name || (*name == 0))
671 /*Allocate RDIR struct. Tidied later with retro_closedir*/
672 if (!(rdir = (libretro_vfs_implementation_dir*)calloc(1, sizeof(*rdir))))
675 rdir->orig_path = strdup(name);
677 copied = strlcpy(path_buf, name, sizeof(path_buf));
679 /* Non-NT platforms don't like extra slashes in the path */
680 if (path_buf[copied - 1] != '\\')
681 path_buf[copied++] = '\\';
683 path_buf[copied] = '*';
684 path_buf[copied + 1] = '\0';
686 path_wide = utf8_to_utf16_string_alloc(path_buf);
687 rdir->directory = FindFirstFileExFromAppW(
688 path_wide, FindExInfoStandard, &rdir->entry,
689 FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH);
695 rdir->entry.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
697 rdir->entry.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
699 if (rdir->directory && rdir != INVALID_HANDLE_VALUE)
702 retro_vfs_closedir_impl(rdir);
706 bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir* rdir)
709 return (FindNextFileW(rdir->directory, &rdir->entry) != 0);
712 return (rdir->directory != INVALID_HANDLE_VALUE);
715 const char* retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir* rdir)
717 char* name = utf16_to_utf8_string_alloc(rdir->entry.cFileName);
718 memset(rdir->entry.cFileName, 0, sizeof(rdir->entry.cFileName));
719 strlcpy((char*)rdir->entry.cFileName, name, sizeof(rdir->entry.cFileName));
722 return (char*)rdir->entry.cFileName;
725 bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir* rdir)
727 const WIN32_FIND_DATA* entry = (const WIN32_FIND_DATA*)&rdir->entry;
728 return entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
731 int retro_vfs_closedir_impl(libretro_vfs_implementation_dir* rdir)
736 if (rdir->directory != INVALID_HANDLE_VALUE)
737 FindClose(rdir->directory);
740 free(rdir->orig_path);
745 void uwp_set_acl(const wchar_t* path, const wchar_t* AccessString)
747 PSECURITY_DESCRIPTOR SecurityDescriptor = NULL;
748 EXPLICIT_ACCESSW ExplicitAccess = { 0 };
749 ACL* AccessControlCurrent = NULL;
750 ACL* AccessControlNew = NULL;
751 SECURITY_INFORMATION SecurityInfo = DACL_SECURITY_INFORMATION;
752 PSID SecurityIdentifier = NULL;
753 HANDLE original_file = CreateFileFromAppW(path,
754 GENERIC_READ | GENERIC_WRITE | WRITE_DAC,
755 FILE_SHARE_READ | FILE_SHARE_WRITE,
756 NULL, OPEN_EXISTING, 0, NULL);
758 if (original_file != INVALID_HANDLE_VALUE)
764 DACL_SECURITY_INFORMATION,
767 &AccessControlCurrent,
773 ConvertStringSidToSidW(AccessString, &SecurityIdentifier);
774 if (SecurityIdentifier)
776 ExplicitAccess.grfAccessPermissions= GENERIC_READ | GENERIC_EXECUTE | GENERIC_WRITE;
777 ExplicitAccess.grfAccessMode = SET_ACCESS;
778 ExplicitAccess.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
779 ExplicitAccess.Trustee.TrusteeForm = TRUSTEE_IS_SID;
780 ExplicitAccess.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
781 ExplicitAccess.Trustee.ptstrName = reinterpret_cast<wchar_t*>(SecurityIdentifier);
787 AccessControlCurrent,
802 if (SecurityDescriptor)
803 LocalFree(reinterpret_cast<HLOCAL>(SecurityDescriptor));
804 if (AccessControlNew)
805 LocalFree(reinterpret_cast<HLOCAL>(AccessControlNew));
806 CloseHandle(original_file);