git subrepo clone https://github.com/libretro/libretro-common.git deps/libretro-common
[pcsx_rearmed.git] / deps / libretro-common / vfs / vfs_implementation_uwp.cpp
1 /* Copyright  (C) 2018-2020 The RetroArch team
2 *
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (vfs_implementation_uwp.cpp).
5 * ---------------------------------------------------------------------------------------
6 *
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:
12 *
13 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14 *
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.
21 */
22
23 #include <retro_environment.h>
24
25 #include <ppl.h>
26 #include <ppltasks.h>
27 #include <stdio.h>
28 #include <wrl.h>
29 #include <wrl/implements.h>
30 #include <robuffer.h>
31 #include <collection.h>
32 #include <functional>
33 #include <fileapifromapp.h>
34 #include <AclAPI.h>
35 #include <sddl.h>
36 #include <io.h>
37 #include <fcntl.h>
38
39 #ifdef RARCH_INTERNAL
40 #ifndef VFS_FRONTEND
41 #define VFS_FRONTEND
42 #endif
43 #endif
44
45 #include <vfs/vfs.h>
46 #include <vfs/vfs_implementation.h>
47 #include <libretro.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>
55
56 namespace
57 {
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)
63    {
64       if (path)
65       {
66          while (*path)
67          {
68             if (*path == '/')
69                *path = '\\';
70             ++path;
71          }
72       }
73    }
74 }
75
76 #ifdef VFS_FRONTEND
77 struct retro_vfs_file_handle
78 #else
79 struct libretro_vfs_implementation_file
80 #endif
81 {
82     int64_t size;
83     uint64_t mappos;
84     uint64_t mapsize;
85     FILE* fp;
86     HANDLE fh;
87     char* buf;
88     char* orig_path;
89     uint8_t* mapped;
90     int fd;
91     unsigned hints;
92     enum vfs_scheme scheme;
93 };
94
95 #define RFILE_HINT_UNBUFFERED (1 << 8)
96
97 int retro_vfs_file_close_impl(libretro_vfs_implementation_file* stream)
98 {
99     if (!stream)
100         return -1;
101
102     if (stream->fp)
103         fclose(stream->fp);
104
105     if (stream->buf != NULL)
106     {
107         free(stream->buf);
108         stream->buf = NULL;
109     }
110     if (stream->orig_path)
111         free(stream->orig_path);
112
113     free(stream);
114
115     return 0;
116 }
117
118 int retro_vfs_file_error_impl(libretro_vfs_implementation_file* stream)
119 {
120     return ferror(stream->fp);
121 }
122
123 int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file* stream)
124 {
125     if (stream)
126         return stream->size;
127     return 0;
128 }
129
130 int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file* stream, int64_t length)
131 {
132    if (stream && _chsize(_fileno(stream->fp), length) == 0)
133       return 0;
134    return -1;
135 }
136
137 int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file* stream)
138 {
139     if (!stream)
140         return -1;
141
142     if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
143         return _ftelli64(stream->fp);
144     if (lseek(stream->fd, 0, SEEK_CUR) < 0)
145         return -1;
146
147     return 0;
148 }
149
150 int64_t retro_vfs_file_seek_internal(
151     libretro_vfs_implementation_file* stream,
152     int64_t offset, int whence)
153 {
154     if (!stream)
155         return -1;
156
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)
160         return -1;
161
162     return 0;
163 }
164
165 int64_t retro_vfs_file_seek_impl(libretro_vfs_implementation_file* stream,
166     int64_t offset, int seek_position)
167 {
168     return retro_vfs_file_seek_internal(stream, offset, seek_position);
169 }
170
171 int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file* stream,
172     void* s, uint64_t len)
173 {
174     if (!stream || (!stream->fp && stream->fh == INVALID_HANDLE_VALUE) || !s)
175       return -1;
176
177    if (stream->fh != INVALID_HANDLE_VALUE)
178    {
179       DWORD _bytes_read;
180       ReadFile(stream->fh, (char*)s, len, &_bytes_read, NULL);
181       return (int64_t)_bytes_read;
182    }
183
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);
187 }
188
189
190 int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file* stream, const void* s, uint64_t len)
191 {
192     if (!stream || (!stream->fp && stream->fh == INVALID_HANDLE_VALUE) || !s)
193         return -1;
194
195     if (stream->fh != INVALID_HANDLE_VALUE)
196     {
197         DWORD bytes_written;
198         WriteFile(stream->fh, s, len, &bytes_written, NULL);
199         return (int64_t)bytes_written;
200     }
201
202     if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
203         return fwrite(s, 1, (size_t)len, stream->fp);
204
205     return write(stream->fd, s, (size_t)len);
206 }
207
208 int retro_vfs_file_flush_impl(libretro_vfs_implementation_file* stream)
209 {
210     if (stream && fflush(stream->fp) == 0)
211        return 0;
212     return -1;
213 }
214
215 int retro_vfs_file_remove_impl(const char *path)
216 {
217    BOOL result;
218    wchar_t *path_wide;
219
220    if (!path || !*path)
221       return -1;
222
223    path_wide = utf8_to_utf16_string_alloc(path);
224    windowsize_path(path_wide);
225
226    /* Try Win32 first, this should work in AppData */
227    result = DeleteFileFromAppW(path_wide);
228    free(path_wide);
229    if (result)
230       return 0;
231
232    return -1;
233 }
234
235 libretro_vfs_implementation_file* retro_vfs_file_open_impl(
236     const char* path, unsigned mode, unsigned hints)
237 {
238     HANDLE file_handle;
239     std::wstring path_wstring;
240     DWORD desireAccess;
241     DWORD creationDisposition;
242     wchar_t                       *path_wide = NULL;
243     int                                flags = 0;
244     const char                     *mode_str = NULL;
245     libretro_vfs_implementation_file* stream =
246         (libretro_vfs_implementation_file*)
247         malloc(sizeof(*stream));
248
249     if (!stream)
250         return NULL;
251
252     stream->fd        = 0;
253     stream->hints     = hints;
254     stream->size      = 0;
255     stream->buf       = NULL;
256     stream->fp        = NULL;
257     stream->fh        = 0;
258     stream->orig_path = NULL;
259     stream->mappos    = 0;
260     stream->mapsize   = 0;
261     stream->mapped    = NULL;
262     stream->scheme    = VFS_SCHEME_NONE;
263
264 #ifdef VFS_FRONTEND
265    if (     path
266          && path[0] == 'v'
267          && path[1] == 'f'
268          && path[2] == 's'
269          && path[3] == 'o'
270          && path[4] == 'n'
271          && path[5] == 'l'
272          && path[6] == 'y'
273          && path[7] == ':'
274          && path[8] == '/'
275          && path[9] == '/')
276          path             += sizeof("vfsonly://")-1;
277 #endif
278
279     path_wide    = utf8_to_utf16_string_alloc(path);
280     windowsize_path(path_wide);
281     path_wstring = path_wide;
282     free(path_wide);
283
284     for (;;)
285     {
286        size_t p = path_wstring.find(L"\\\\");
287        if (p == std::wstring::npos)
288           break;
289        path_wstring.replace(p, 2, L"\\");
290     }
291
292     path_wstring      = L"\\\\?\\" + path_wstring;
293     stream->orig_path = strdup(path);
294
295     stream->hints    &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS;
296
297     switch (mode)
298     {
299        case RETRO_VFS_FILE_ACCESS_READ:
300           mode_str = "rb";
301           flags    = O_RDONLY | O_BINARY;
302           break;
303
304        case RETRO_VFS_FILE_ACCESS_WRITE:
305           mode_str = "wb";
306           flags    = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
307           break;
308
309        case RETRO_VFS_FILE_ACCESS_READ_WRITE:
310           mode_str = "w+b";
311           flags    = O_RDWR | O_CREAT | O_TRUNC | O_BINARY;
312           break;
313
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:
316           mode_str = "r+b";
317           flags    = O_RDWR | O_BINARY;
318           break;
319
320        default:
321           goto error;
322     }
323
324     switch (mode)
325     {
326        case RETRO_VFS_FILE_ACCESS_READ_WRITE:
327           desireAccess = GENERIC_READ | GENERIC_WRITE;
328           break;
329        case RETRO_VFS_FILE_ACCESS_WRITE:
330           desireAccess = GENERIC_WRITE;
331           break;
332        case RETRO_VFS_FILE_ACCESS_READ:
333           desireAccess = GENERIC_READ;
334           break;
335     }
336     if (mode == RETRO_VFS_FILE_ACCESS_READ)
337         creationDisposition = OPEN_EXISTING;
338     else
339         creationDisposition = (mode & RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING) != 0 
340            ? OPEN_ALWAYS
341            : CREATE_ALWAYS;
342
343     if ((file_handle = CreateFile2FromAppW(path_wstring.data(), desireAccess,
344                 FILE_SHARE_READ, creationDisposition, NULL)) == INVALID_HANDLE_VALUE)
345        goto error;
346
347     stream->fh      = file_handle;
348     if ((stream->fd = _open_osfhandle((uint64)stream->fh, flags)) == -1)
349         goto error;
350
351     {
352         FILE *fp = _fdopen(stream->fd, mode_str);
353
354         if (!fp)
355             goto error;
356         stream->fp = fp;
357     }
358
359     /* Regarding setvbuf:
360         *
361         * https://www.freebsd.org/cgi/man.cgi?query=setvbuf&apropos=0&sektion=0&manpath=FreeBSD+11.1-RELEASE&arch=default&format=html
362         *
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.
366         *
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.
369         */
370         /* TODO: this is only useful for a few platforms,
371         * find which and add ifdef */
372     if (stream->scheme != VFS_SCHEME_CDROM)
373     {
374         stream->buf = (char*)calloc(1, 0x4000);
375         if (stream->fp)
376             setvbuf(stream->fp, stream->buf, _IOFBF, 0x4000);
377     }
378
379     retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
380     retro_vfs_file_seek_internal(stream, 0, SEEK_END);
381
382     stream->size = retro_vfs_file_tell_impl(stream);
383
384     retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
385
386     return stream;
387
388 error:
389     retro_vfs_file_close_impl(stream);
390     return NULL;
391 }
392
393 static int uwp_mkdir_impl(std::experimental::filesystem::path dir)
394 {
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;
399
400     if (dir.empty())
401         return -1;
402
403     /* Check if file attributes can be gotten successfully  */
404     if (GetFileAttributesExFromAppW(dir.parent_path().wstring().c_str(), GetFileExInfoStandard, &lpFileInfo))
405     {
406         /* Check that the files attributes are not null or empty */
407         if (lpFileInfo.dwFileAttributes != INVALID_FILE_ATTRIBUTES && lpFileInfo.dwFileAttributes != 0)
408         {
409             if (lpFileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
410                 parent_dir_exists = true;
411         }
412     }
413     if (!parent_dir_exists)
414     {
415         /* Try to create parent dir */
416         int success = uwp_mkdir_impl(dir.parent_path());
417         if (success != 0 && success != -2)
418             return success;
419     }
420
421
422     /* Try Win32 first, this should work in AppData */
423     if (CreateDirectoryFromAppW(dir.wstring().c_str(), NULL))
424         return 0;
425
426     if (GetLastError() == ERROR_ALREADY_EXISTS)
427         return -2;
428
429     return -1;
430 }
431
432 int retro_vfs_mkdir_impl(const char* dir)
433 {
434     return uwp_mkdir_impl(std::filesystem::path(dir));
435 }
436
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.
441  *
442  * This will fail even if a single file cannot be moved.
443  */
444 static int uwp_move_path(
445       std::filesystem::path old_path,
446       std::filesystem::path new_path,
447       bool firstrun)
448 {
449     if (old_path.empty() || new_path.empty())
450         return -1;
451
452     if (firstrun)
453     {
454         WIN32_FILE_ATTRIBUTE_DATA lpFileInfo, targetfileinfo;
455         bool parent_dir_exists = false;
456
457         /* Make sure that parent path exists */
458         if (GetFileAttributesExFromAppW(
459                  new_path.parent_path().wstring().c_str(),
460                  GetFileExInfoStandard, &lpFileInfo))
461         {
462             /* Check that the files attributes are not null or empty */
463             if (     lpFileInfo.dwFileAttributes 
464                   != INVALID_FILE_ATTRIBUTES 
465                   && lpFileInfo.dwFileAttributes != 0)
466             {
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());
470             }
471         }
472
473         /* Make sure that source path exists */
474         if (GetFileAttributesExFromAppW(old_path.wstring().c_str(), GetFileExInfoStandard, &lpFileInfo))
475         {
476             /* Check that the files attributes are not null or empty */
477             if (     lpFileInfo.dwFileAttributes != INVALID_FILE_ATTRIBUTES 
478                   && lpFileInfo.dwFileAttributes != 0)
479             {
480                 /* Check if source path is a dir */
481                 if (lpFileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
482                 {
483                    int result;
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)
489                       return result;
490                 }
491                 else
492                 {
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,
498                             &targetfileinfo))
499                     {
500                         if (
501                                  targetfileinfo.dwFileAttributes != INVALID_FILE_ATTRIBUTES
502                               && targetfileinfo.dwFileAttributes != 0
503                               && (!(targetfileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)))
504                         {
505                             if (DeleteFileFromAppW(new_path.wstring().c_str()))
506                                 return -1;
507                         }
508                     }
509
510                     if (!MoveFileFromAppW(old_path.wstring().c_str(),
511                              new_path.wstring().c_str()))
512                         return -1;
513                     /* Set ACL */
514                     uwp_set_acl(new_path.wstring().c_str(), L"S-1-15-2-1");
515                 }
516             }
517         }
518
519     }
520     else
521     {
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);
531
532        if (searchResults != INVALID_HANDLE_VALUE)
533        {
534           bool fail = false;
535           do
536           {
537              if (     wcscmp(findDataResult.cFileName, L".")  != 0 
538                    && wcscmp(findDataResult.cFileName, L"..") != 0)
539              {
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)
546                 {
547                    CreateDirectoryFromAppW(temp_new.wstring().c_str(), NULL);
548                    if (uwp_move_path(temp_old, temp_new, false) != 0)
549                       fail = true;
550                 }
551                 else
552                 {
553                    WIN32_FILE_ATTRIBUTE_DATA targetfileinfo;
554                    /* The file that we want to move exists so we can copy
555                     * it now.
556                     * Check if target file already exists. */
557                    if (GetFileAttributesExFromAppW(temp_new.wstring().c_str(),
558                             GetFileExInfoStandard, &targetfileinfo))
559                    {
560                       if (
561                                (targetfileinfo.dwFileAttributes !=
562                                 INVALID_FILE_ATTRIBUTES)
563                             && (targetfileinfo.dwFileAttributes != 0)
564                             && (!(targetfileinfo.dwFileAttributes 
565                                   & FILE_ATTRIBUTE_DIRECTORY)))
566                       {
567                          if (DeleteFileFromAppW(temp_new.wstring().c_str()))
568                             fail = true;
569                       }
570                    }
571
572                    if (!MoveFileFromAppW(temp_old.wstring().c_str(),
573                             temp_new.wstring().c_str()))
574                       fail = true;
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
578                     * though */
579                    uwp_set_acl(temp_new.wstring().c_str(), L"S-1-15-2-1");
580                 }
581              }
582           } while (FindNextFile(searchResults, &findDataResult));
583           FindClose(searchResults);
584           if (fail)
585              return -1;
586        }
587        free(filteredPath);
588     }
589     return 0;
590 }
591
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)
596 {
597     return uwp_move_path(std::filesystem::path(old_path),
598           std::filesystem::path(old_path), true);
599 }
600
601 const char *retro_vfs_file_get_path_impl(libretro_vfs_implementation_file *stream)
602 {
603    /* Should never happen, do something noisy so caller can be fixed */
604    if (!stream)
605       abort();
606    return stream->orig_path;
607 }
608
609 int retro_vfs_stat_impl(const char *path, int32_t *size)
610 {
611    wchar_t *path_wide;
612    _WIN32_FILE_ATTRIBUTE_DATA attribdata;
613
614    if (!path || !*path)
615       return 0;
616
617    path_wide = utf8_to_utf16_string_alloc(path);
618    windowsize_path(path_wide);
619
620    /* Try Win32 first, this should work in AppData */
621    if (GetFileAttributesExFromAppW(path_wide,
622             GetFileExInfoStandard, &attribdata))
623    {
624        if (attribdata.dwFileAttributes != INVALID_FILE_ATTRIBUTES)
625        {
626            if (!(attribdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
627            {
628                LARGE_INTEGER sz;
629                if (size)
630                {
631                    sz.HighPart = attribdata.nFileSizeHigh;
632                    sz.LowPart = attribdata.nFileSizeLow;
633                    *size = sz.QuadPart;
634                }
635            }
636            free(path_wide);
637            return (attribdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 
638               ? RETRO_VFS_STAT_IS_VALID | RETRO_VFS_STAT_IS_DIRECTORY 
639               : RETRO_VFS_STAT_IS_VALID;
640        }
641    }
642    free(path_wide);
643    return 0;
644 }
645
646 #ifdef VFS_FRONTEND
647 struct retro_vfs_dir_handle
648 #else
649 struct libretro_vfs_implementation_dir
650 #endif
651 {
652     char* orig_path;
653     WIN32_FIND_DATAW entry;
654     HANDLE directory;
655     bool next;
656     char path[PATH_MAX_LENGTH];
657 };
658
659 libretro_vfs_implementation_dir* retro_vfs_opendir_impl(
660     const char* name, bool include_hidden)
661 {
662     char path_buf[1024];
663     size_t copied      = 0;
664     wchar_t* path_wide = NULL;
665     libretro_vfs_implementation_dir* rdir;
666
667     /* Reject NULL or empty string paths*/
668     if (!name || (*name == 0))
669         return NULL;
670
671     /*Allocate RDIR struct. Tidied later with retro_closedir*/
672     if (!(rdir = (libretro_vfs_implementation_dir*)calloc(1, sizeof(*rdir))))
673         return NULL;
674
675     rdir->orig_path = strdup(name);
676
677     copied          = strlcpy(path_buf, name, sizeof(path_buf));
678
679     /* Non-NT platforms don't like extra slashes in the path */
680     if (path_buf[copied - 1] != '\\')
681         path_buf[copied++] = '\\';
682
683     path_buf[copied]       = '*';
684     path_buf[copied + 1]   = '\0';
685
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);
690
691     if (path_wide)
692         free(path_wide);
693
694     if (include_hidden)
695         rdir->entry.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
696     else
697         rdir->entry.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
698
699     if (rdir->directory && rdir != INVALID_HANDLE_VALUE)
700         return rdir;
701
702     retro_vfs_closedir_impl(rdir);
703     return NULL;
704 }
705
706 bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir* rdir)
707 {
708     if (rdir->next)
709         return (FindNextFileW(rdir->directory, &rdir->entry) != 0);
710
711     rdir->next = true;
712     return (rdir->directory != INVALID_HANDLE_VALUE);
713 }
714
715 const char* retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir* rdir)
716 {
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));
720     if (name)
721         free(name);
722     return (char*)rdir->entry.cFileName;
723 }
724
725 bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir* rdir)
726 {
727     const WIN32_FIND_DATA* entry = (const WIN32_FIND_DATA*)&rdir->entry;
728     return entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
729 }
730
731 int retro_vfs_closedir_impl(libretro_vfs_implementation_dir* rdir)
732 {
733     if (!rdir)
734         return -1;
735
736     if (rdir->directory != INVALID_HANDLE_VALUE)
737         FindClose(rdir->directory);
738
739     if (rdir->orig_path)
740         free(rdir->orig_path);
741     free(rdir);
742     return 0;
743 }
744
745 void uwp_set_acl(const wchar_t* path, const wchar_t* AccessString)
746 {
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);
757
758     if (original_file != INVALID_HANDLE_VALUE)
759     {
760        if (
761              GetSecurityInfo(
762                 original_file,
763                 SE_FILE_OBJECT,
764                 DACL_SECURITY_INFORMATION,
765                 NULL,
766                 NULL,
767                 &AccessControlCurrent,
768                 NULL,
769                 &SecurityDescriptor
770                 ) == ERROR_SUCCESS
771           )
772        {
773           ConvertStringSidToSidW(AccessString, &SecurityIdentifier);
774           if (SecurityIdentifier)
775           {
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);
782
783              if (
784                    SetEntriesInAclW(
785                       1,
786                       &ExplicitAccess,
787                       AccessControlCurrent,
788                       &AccessControlNew
789                       ) == ERROR_SUCCESS
790                 )
791                 SetSecurityInfo(
792                       original_file,
793                       SE_FILE_OBJECT,
794                       SecurityInfo,
795                       NULL,
796                       NULL,
797                       AccessControlNew,
798                       NULL
799                       );
800           }
801        }
802        if (SecurityDescriptor)
803           LocalFree(reinterpret_cast<HLOCAL>(SecurityDescriptor));
804        if (AccessControlNew)
805           LocalFree(reinterpret_cast<HLOCAL>(AccessControlNew));
806        CloseHandle(original_file);
807     }
808 }