Commit | Line | Data |
---|---|---|
3719602c PC |
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 | } |