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 <windows.storage.streams.h>
31 #include <robuffer.h>
32 #include <collection.h>
33 #include <functional>
34
35 using namespace Windows::Foundation;
36 using namespace Windows::Foundation::Collections;
37 using namespace Windows::Storage;
38 using namespace Windows::Storage::Streams;
39 using namespace Windows::Storage::FileProperties;
40
41 #ifdef RARCH_INTERNAL
42 #ifndef VFS_FRONTEND
43 #define VFS_FRONTEND
44 #endif
45 #endif
46
47 #include <vfs/vfs.h>
48 #include <vfs/vfs_implementation.h>
49 #include <libretro.h>
50 #include <encodings/utf.h>
51 #include <retro_miscellaneous.h>
52 #include <file/file_path.h>
53 #include <retro_assert.h>
54 #include <string/stdstring.h>
55 #include <retro_environment.h>
56 #include <uwp/uwp_async.h>
57 #include <uwp/uwp_file_handle_access.h>
58
59 namespace
60 {
windowsize_path(wchar_t * path)61 void windowsize_path(wchar_t* path)
62 {
63 /* UWP deals with paths containing / instead of
64 * \ way worse than normal Windows */
65 /* and RetroArch may sometimes mix them
66 * (e.g. on archive extraction) */
67 if (!path)
68 return;
69
70 while (*path)
71 {
72 if (*path == '/')
73 *path = '\\';
74 ++path;
75 }
76 }
77 }
78
79 namespace
80 {
81 /* Damn you, UWP, why no functions for that either */
82 template<typename T>
83 concurrency::task<T^> GetItemFromPathAsync(Platform::String^ path)
84 {
85 static_assert(false, "StorageFile and StorageFolder only");
86 }
87 template<>
88 concurrency::task<StorageFile^> GetItemFromPathAsync(Platform::String^ path)
89 {
90 return concurrency::create_task(StorageFile::GetFileFromPathAsync(path));
91 }
92 template<>
93 concurrency::task<StorageFolder^> GetItemFromPathAsync(Platform::String^ path)
94 {
95 return concurrency::create_task(StorageFolder::GetFolderFromPathAsync(path));
96 }
97
98 template<typename T>
99 concurrency::task<T^> GetItemInFolderFromPathAsync(StorageFolder^ folder, Platform::String^ path)
100 {
101 static_assert(false, "StorageFile and StorageFolder only");
102 }
103 template<>
104 concurrency::task<StorageFile^> GetItemInFolderFromPathAsync(StorageFolder^ folder, Platform::String^ path)
105 {
106 if (path->IsEmpty())
107 retro_assert(false); /* Attempt to read a folder as a file - this really should have been caught earlier */
108 return concurrency::create_task(folder->GetFileAsync(path));
109 }
110 template<>
111 concurrency::task<StorageFolder^> GetItemInFolderFromPathAsync(StorageFolder^ folder, Platform::String^ path)
112 {
113 if (path->IsEmpty())
__anon35343c680302() 114 return concurrency::create_task(concurrency::create_async([folder]() { return folder; }));
115 return concurrency::create_task(folder->GetFolderAsync(path));
116 }
117 }
118
119 namespace
120 {
121 /* A list of all StorageFolder objects returned using from the file picker */
122 Platform::Collections::Vector<StorageFolder^> accessible_directories;
123
TriggerPickerAddDialog()124 concurrency::task<Platform::String^> TriggerPickerAddDialog()
125 {
126 auto folderPicker = ref new Windows::Storage::Pickers::FolderPicker();
127 folderPicker->SuggestedStartLocation = Windows::Storage::Pickers::PickerLocationId::Desktop;
128 folderPicker->FileTypeFilter->Append("*");
129
130 return concurrency::create_task(folderPicker->PickSingleFolderAsync()).then([](StorageFolder^ folder) {
131 if (folder == nullptr)
132 throw ref new Platform::Exception(E_ABORT, L"Operation cancelled by user");
133
134 /* TODO: check for duplicates */
135 accessible_directories.Append(folder);
136 return folder->Path;
137 });
138 }
139
140 template<typename T>
141 concurrency::task<T^> LocateStorageItem(Platform::String^ path)
142 {
143 /* Look for a matching directory we can use */
144 for each (StorageFolder^ folder in accessible_directories)
145 {
146 std::wstring file_path;
147 std::wstring folder_path = folder->Path->Data();
148 size_t folder_path_size = folder_path.size();
149 /* Could be C:\ or C:\Users\somebody - remove the trailing slash to unify them */
150 if (folder_path[folder_path_size - 1] == '\\')
151 folder_path.erase(folder_path_size - 1);
152
153 file_path = path->Data();
154
155 if (file_path.find(folder_path) == 0)
156 {
157 /* Found a match */
158 file_path = file_path.length() > folder_path.length()
159 ? file_path.substr(folder_path.length() + 1)
160 : L"";
161 return concurrency::create_task(GetItemInFolderFromPathAsync<T>(folder, ref new Platform::String(file_path.data())));
162 }
163 }
164
165 /* No matches - try accessing directly, and fallback to user prompt */
__anon35343c680602(concurrency::task<T^> item) 166 return concurrency::create_task(GetItemFromPathAsync<T>(path)).then([&](concurrency::task<T^> item) {
167 try
168 {
169 T^ storageItem = item.get();
170 return concurrency::create_task(concurrency::create_async([storageItem]() { return storageItem; }));
171 }
172 catch (Platform::AccessDeniedException^ e)
173 {
174 Windows::UI::Popups::MessageDialog^ dialog =
175 ref new Windows::UI::Popups::MessageDialog("Path \"" + path + "\" is not currently accessible. Please open any containing directory to access it.");
176 dialog->Commands->Append(ref new Windows::UI::Popups::UICommand("Open file picker"));
177 dialog->Commands->Append(ref new Windows::UI::Popups::UICommand("Cancel"));
178 return concurrency::create_task(dialog->ShowAsync()).then([path](Windows::UI::Popups::IUICommand^ cmd) {
179 if (cmd->Label == "Open file picker")
180 {
181 return TriggerPickerAddDialog().then([path](Platform::String^ added_path) {
182 /* Retry */
183 return LocateStorageItem<T>(path);
184 });
185 }
186 else
187 {
188 throw ref new Platform::Exception(E_ABORT, L"Operation cancelled by user");
189 }
190 });
191 }
192 });
193 }
194
195 IStorageItem^ LocateStorageFileOrFolder(Platform::String^ path)
196 {
197 if (!path || path->IsEmpty())
198 return nullptr;
199
200 if (*(path->End() - 1) == '\\')
201 {
202 /* Ends with a slash, so it's definitely a directory */
__anon35343c680a02() 203 return RunAsyncAndCatchErrors<StorageFolder^>([&]() {
204 return concurrency::create_task(LocateStorageItem<StorageFolder>(path));
205 }, nullptr);
206 }
207 else
208 {
209 /* No final slash - probably a file (since RetroArch usually slash-terminates dirs), but there is still a chance it's a directory */
210 IStorageItem^ item;
__anon35343c680b02() 211 item = RunAsyncAndCatchErrors<StorageFile^>([&]() {
212 return concurrency::create_task(LocateStorageItem<StorageFile>(path));
213 }, nullptr);
214 if (!item)
215 {
__anon35343c680c02() 216 item = RunAsyncAndCatchErrors<StorageFolder^>([&]() {
217 return concurrency::create_task(LocateStorageItem<StorageFolder>(path));
218 }, nullptr);
219 }
220 return item;
221 }
222 }
223 }
224
225
226 /* This is some pure magic and I have absolutely no idea how it works */
227 /* Wraps a raw buffer into a WinRT object */
228 /* https://stackoverflow.com/questions/10520335/how-to-wrap-a-char-buffer-in-a-winrt-ibuffer-in-c */
229 class NativeBuffer :
230 public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::WinRtClassicComMix>,
231 ABI::Windows::Storage::Streams::IBuffer,
232 Windows::Storage::Streams::IBufferByteAccess>
233 {
234 public:
~NativeBuffer()235 virtual ~NativeBuffer()
236 {
237 }
238
RuntimeClassInitialize(byte * buffer,uint32_t capacity,uint32_t length)239 HRESULT __stdcall RuntimeClassInitialize(
240 byte *buffer, uint32_t capacity, uint32_t length)
241 {
242 m_buffer = buffer;
243 m_capacity = capacity;
244 m_length = length;
245 return S_OK;
246 }
247
Buffer(byte ** value)248 HRESULT __stdcall Buffer(byte **value)
249 {
250 if (m_buffer == nullptr)
251 return E_INVALIDARG;
252 *value = m_buffer;
253 return S_OK;
254 }
255
get_Capacity(uint32_t * value)256 HRESULT __stdcall get_Capacity(uint32_t *value)
257 {
258 *value = m_capacity;
259 return S_OK;
260 }
261
get_Length(uint32_t * value)262 HRESULT __stdcall get_Length(uint32_t *value)
263 {
264 *value = m_length;
265 return S_OK;
266 }
267
put_Length(uint32_t value)268 HRESULT __stdcall put_Length(uint32_t value)
269 {
270 if (value > m_capacity)
271 return E_INVALIDARG;
272 m_length = value;
273 return S_OK;
274 }
275
276 private:
277 byte *m_buffer;
278 uint32_t m_capacity;
279 uint32_t m_length;
280 };
281
282 IBuffer^ CreateNativeBuffer(void* buf, uint32_t capacity, uint32_t length)
283 {
284 Microsoft::WRL::ComPtr<NativeBuffer> nativeBuffer;
285 Microsoft::WRL::Details::MakeAndInitialize<NativeBuffer>(&nativeBuffer, (byte *)buf, capacity, length);
286 auto iinspectable = (IInspectable *)reinterpret_cast<IInspectable *>(nativeBuffer.Get());
287 IBuffer ^buffer = reinterpret_cast<IBuffer ^>(iinspectable);
288 return buffer;
289 }
290
291 /* Get a Win32 file handle out of IStorageFile */
292 /* https://stackoverflow.com/questions/42799235/how-can-i-get-a-win32-handle-for-a-storagefile-or-storagefolder-in-uwp */
293 HRESULT GetHandleFromStorageFile(Windows::Storage::StorageFile^ file, HANDLE* handle, HANDLE_ACCESS_OPTIONS accessMode)
294 {
295 Microsoft::WRL::ComPtr<IUnknown> abiPointer(reinterpret_cast<IUnknown*>(file));
296 Microsoft::WRL::ComPtr<IStorageItemHandleAccess> handleAccess;
297 if (SUCCEEDED(abiPointer.As(&handleAccess)))
298 {
299 HANDLE hFile = INVALID_HANDLE_VALUE;
300
301 if (SUCCEEDED(handleAccess->Create(accessMode,
302 HANDLE_SHARING_OPTIONS::HSO_SHARE_READ,
303 HANDLE_OPTIONS::HO_NONE,
304 nullptr,
305 &hFile)))
306 {
307 *handle = hFile;
308 return S_OK;
309 }
310 }
311
312 return E_FAIL;
313 }
314
315 #ifdef VFS_FRONTEND
316 struct retro_vfs_file_handle
317 #else
318 struct libretro_vfs_implementation_file
319 #endif
320 {
321 IRandomAccessStream^ fp;
322 IBuffer^ bufferp;
323 HANDLE file_handle;
324 char* buffer;
325 char* orig_path;
326 size_t buffer_size;
327 int buffer_left;
328 size_t buffer_fill;
329 };
330
retro_vfs_file_open_impl(const char * path,unsigned mode,unsigned hints)331 libretro_vfs_implementation_file *retro_vfs_file_open_impl(
332 const char *path, unsigned mode, unsigned hints)
333 {
334 char dirpath[PATH_MAX_LENGTH];
335 char filename[PATH_MAX_LENGTH];
336 wchar_t *path_wide;
337 wchar_t *dirpath_wide;
338 wchar_t *filename_wide;
339 Platform::String^ path_str;
340 Platform::String^ filename_str;
341 Platform::String^ dirpath_str;
342 HANDLE file_handle = INVALID_HANDLE_VALUE;
343 DWORD desireAccess;
344 DWORD creationDisposition;
345
346 if (!path || !*path)
347 return NULL;
348
349 /* Something tried to access files from current directory.
350 * This is not allowed on UWP. */
351 if (!path_is_absolute(path))
352 return NULL;
353
354 /* Trying to open a directory as file?! */
355 if (PATH_CHAR_IS_SLASH(path[strlen(path) - 1]))
356 return NULL;
357
358 dirpath[0] = filename[0] = '\0';
359
360 path_wide = utf8_to_utf16_string_alloc(path);
361 windowsize_path(path_wide);
362 path_str = ref new Platform::String(path_wide);
363 free(path_wide);
364
365 fill_pathname_basedir(dirpath, path, sizeof(dirpath));
366 dirpath_wide = utf8_to_utf16_string_alloc(dirpath);
367 windowsize_path(dirpath_wide);
368 dirpath_str = ref new Platform::String(dirpath_wide);
369 free(dirpath_wide);
370
371 fill_pathname_base(filename, path, sizeof(filename));
372 filename_wide = utf8_to_utf16_string_alloc(filename);
373 filename_str = ref new Platform::String(filename_wide);
374 free(filename_wide);
375
376 retro_assert(!dirpath_str->IsEmpty() && !filename_str->IsEmpty());
377
378 /* Try Win32 first, this should work in AppData */
379 if (mode == RETRO_VFS_FILE_ACCESS_READ)
380 {
381 desireAccess = GENERIC_READ;
382 creationDisposition = OPEN_ALWAYS;
383 }
384 else
385 {
386 desireAccess = GENERIC_WRITE;
387 creationDisposition = (mode & RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING) != 0 ?
388 OPEN_ALWAYS : CREATE_ALWAYS;
389 }
390
391 file_handle = CreateFile2(path_str->Data(), desireAccess, FILE_SHARE_READ, creationDisposition, NULL);
392
393 if (file_handle != INVALID_HANDLE_VALUE)
394 {
395 libretro_vfs_implementation_file* stream = (libretro_vfs_implementation_file*)calloc(1, sizeof(*stream));
396 if (!stream)
397 return (libretro_vfs_implementation_file*)NULL;
398
399 stream->orig_path = strdup(path);
400 stream->fp = nullptr;
401 stream->file_handle = file_handle;
402 stream->buffer_left = 0;
403 stream->buffer_fill = 0;
404 return stream;
405 }
406
407 /* Fallback to WinRT */
408 return RunAsyncAndCatchErrors<libretro_vfs_implementation_file*>([&]() {
409 return concurrency::create_task(LocateStorageItem<StorageFolder>(dirpath_str)).then([&](StorageFolder^ dir) {
410 if (mode == RETRO_VFS_FILE_ACCESS_READ)
411 return dir->GetFileAsync(filename_str);
412 else
413 return dir->CreateFileAsync(filename_str, (mode & RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING) != 0 ?
414 CreationCollisionOption::OpenIfExists : CreationCollisionOption::ReplaceExisting);
415 }).then([&](StorageFile ^file) {
416
417 HANDLE_CREATION_OPTIONS creationOptions;
418 HANDLE_ACCESS_OPTIONS handleAccess;
419 HRESULT hr;
420
421 /* Try to use IStorageItemHandleAccess to get the file handle,
422 * with that we can use Win32 APIs for subsequent reads/writes
423 */
424 if (mode == RETRO_VFS_FILE_ACCESS_READ)
425 {
426 handleAccess = HANDLE_ACCESS_OPTIONS::HAO_READ;
427 creationOptions = HANDLE_CREATION_OPTIONS::HCO_OPEN_ALWAYS;
428 }
429 else
430 {
431 handleAccess = HANDLE_ACCESS_OPTIONS::HAO_WRITE;
432 creationOptions = (mode & RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING) != 0 ?
433 HANDLE_CREATION_OPTIONS::HCO_OPEN_ALWAYS : HANDLE_CREATION_OPTIONS::HCO_CREATE_ALWAYS;
434
435 }
436 hr = GetHandleFromStorageFile(file, &file_handle, handleAccess);
437
438 if (SUCCEEDED(hr))
439 /* Success, let's return a null pointer and continue */
440 return concurrency::create_task([&]() { return (IRandomAccessStream^) nullptr; });
441 else
442 {
443 /* Failed, open a WinRT buffer of the file */
444 FileAccessMode accessMode = (mode == RETRO_VFS_FILE_ACCESS_READ) ?
445 FileAccessMode::Read : FileAccessMode::ReadWrite;
446 return concurrency::create_task(file->OpenAsync(accessMode));
447 }
448 }).then([&](IRandomAccessStream^ fpstream) {
449 libretro_vfs_implementation_file *stream = (libretro_vfs_implementation_file*)calloc(1, sizeof(*stream));
450 if (!stream)
451 return (libretro_vfs_implementation_file*)NULL;
452
453 stream->orig_path = strdup(path);
454 stream->fp = fpstream;
455 stream->file_handle = file_handle;
456 stream->buffer_left = 0;
457 stream->buffer_fill = 0;
458
459 if (fpstream)
460 {
461 /* We are using WinRT.
462 * Preallocate a small buffer for manually buffered I/O,
463 * makes short read faster */
464 stream->fp->Seek(0);
465 int buf_size = 8 * 1024;
466 stream->buffer = (char*)malloc(buf_size);
467 stream->bufferp = CreateNativeBuffer(stream->buffer, buf_size, 0);
468 stream->buffer_size = buf_size;
469 }
470 else
471 {
472 /* If we can use Win32 file API, buffering shouldn't be necessary */
473 stream->buffer = NULL;
474 stream->bufferp = nullptr;
475 stream->buffer_size = 0;
476 }
477 return stream;
478 });
479 }, NULL);
480 }
481
retro_vfs_file_close_impl(libretro_vfs_implementation_file * stream)482 int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream)
483 {
484 if (!stream || (!stream->fp && stream->file_handle == INVALID_HANDLE_VALUE))
485 return -1;
486
487 if (stream->file_handle != INVALID_HANDLE_VALUE)
488 CloseHandle(stream->file_handle);
489 else
490 {
491 /* Apparently, this is how you close a file in WinRT */
492 /* Yes, really */
493 stream->fp = nullptr;
494 free(stream->buffer);
495 }
496
497 return 0;
498 }
499
retro_vfs_file_error_impl(libretro_vfs_implementation_file * stream)500 int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream)
501 {
502 return false; /* TODO */
503 }
504
retro_vfs_file_size_impl(libretro_vfs_implementation_file * stream)505 int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file *stream)
506 {
507 if (!stream || (!stream->fp && stream->file_handle == INVALID_HANDLE_VALUE))
508 return 0;
509
510 if (stream->file_handle != INVALID_HANDLE_VALUE)
511 {
512 LARGE_INTEGER sz;
513 if (GetFileSizeEx(stream->file_handle, &sz))
514 return sz.QuadPart;
515 return 0;
516 }
517
518 return stream->fp->Size;
519 }
520
retro_vfs_file_truncate_impl(libretro_vfs_implementation_file * stream,int64_t length)521 int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, int64_t length)
522 {
523 if (!stream || (!stream->fp && stream->file_handle == INVALID_HANDLE_VALUE))
524 return -1;
525
526 if (stream->file_handle != INVALID_HANDLE_VALUE)
527 {
528 int64_t p = retro_vfs_file_tell_impl(stream);
529 retro_vfs_file_seek_impl(stream, length, RETRO_VFS_SEEK_POSITION_START);
530 SetEndOfFile(stream->file_handle);
531
532 if (p < length)
533 retro_vfs_file_seek_impl(stream, p, RETRO_VFS_SEEK_POSITION_START);
534 }
535 else
536 stream->fp->Size = length;
537
538 return 0;
539 }
540
retro_vfs_file_tell_impl(libretro_vfs_implementation_file * stream)541 int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream)
542 {
543 LARGE_INTEGER _offset;
544 LARGE_INTEGER out;
545 _offset.QuadPart = 0;
546
547 if (!stream || (!stream->fp && stream->file_handle == INVALID_HANDLE_VALUE))
548 return -1;
549
550 if (stream->file_handle != INVALID_HANDLE_VALUE)
551 {
552 SetFilePointerEx(stream->file_handle, _offset, &out, FILE_CURRENT);
553 return out.QuadPart;
554 }
555
556 return stream->fp->Position - stream->buffer_left;
557 }
558
retro_vfs_file_seek_impl(libretro_vfs_implementation_file * stream,int64_t offset,int seek_position)559 int64_t retro_vfs_file_seek_impl(
560 libretro_vfs_implementation_file *stream,
561 int64_t offset, int seek_position)
562 {
563 LARGE_INTEGER _offset;
564 _offset.QuadPart = offset;
565
566 if (!stream || (!stream->fp && stream->file_handle == INVALID_HANDLE_VALUE))
567 return -1;
568
569 switch (seek_position)
570 {
571 case RETRO_VFS_SEEK_POSITION_START:
572 if (stream->file_handle != INVALID_HANDLE_VALUE)
573 SetFilePointerEx(stream->file_handle, _offset, NULL, FILE_BEGIN);
574 else
575 stream->fp->Seek(offset);
576 break;
577
578 case RETRO_VFS_SEEK_POSITION_CURRENT:
579 if (stream->file_handle != INVALID_HANDLE_VALUE)
580 SetFilePointerEx(stream->file_handle, _offset, NULL, FILE_CURRENT);
581 else
582 stream->fp->Seek(retro_vfs_file_tell_impl(stream) + offset);
583 break;
584
585 case RETRO_VFS_SEEK_POSITION_END:
586 if (stream->file_handle != INVALID_HANDLE_VALUE)
587 SetFilePointerEx(stream->file_handle, _offset, NULL, FILE_END);
588 else
589 stream->fp->Seek(stream->fp->Size - offset);
590 break;
591 }
592
593 /* For simplicity always flush the buffer on seek */
594 stream->buffer_left = 0;
595
596 return 0;
597 }
598
retro_vfs_file_read_impl(libretro_vfs_implementation_file * stream,void * s,uint64_t len)599 int64_t retro_vfs_file_read_impl(
600 libretro_vfs_implementation_file *stream, void *s, uint64_t len)
601 {
602 int64_t ret;
603 int64_t bytes_read = 0;
604 IBuffer^ buffer;
605
606 if (!stream || (!stream->fp && stream->file_handle == INVALID_HANDLE_VALUE) || !s)
607 return -1;
608
609 if (stream->file_handle != INVALID_HANDLE_VALUE)
610 {
611 DWORD _bytes_read;
612 ReadFile(stream->file_handle, (char*)s, len, &_bytes_read, NULL);
613 return (int64_t)_bytes_read;
614 }
615
616 if (len <= stream->buffer_size)
617 {
618 /* Small read, use manually buffered I/O */
619 if (stream->buffer_left < len)
620 {
621 /* Exhaust the buffer */
622 memcpy(s,
623 &stream->buffer[stream->buffer_fill - stream->buffer_left],
624 stream->buffer_left);
625 len -= stream->buffer_left;
626 bytes_read += stream->buffer_left;
627 stream->buffer_left = 0;
628
629 /* Fill buffer */
630 stream->buffer_left = RunAsyncAndCatchErrors<int64_t>([&]() {
631 return concurrency::create_task(stream->fp->ReadAsync(stream->bufferp, stream->bufferp->Capacity, InputStreamOptions::None)).then([&](IBuffer^ buf) {
632 retro_assert(stream->bufferp == buf);
633 return (int64_t)stream->bufferp->Length;
634 });
635 }, -1);
636 stream->buffer_fill = stream->buffer_left;
637
638 if (stream->buffer_left == -1)
639 {
640 stream->buffer_left = 0;
641 stream->buffer_fill = 0;
642 return -1;
643 }
644
645 if (stream->buffer_left < len)
646 {
647 /* EOF */
648 memcpy(&((char*)s)[bytes_read],
649 stream->buffer, stream->buffer_left);
650 bytes_read += stream->buffer_left;
651 stream->buffer_left = 0;
652 return bytes_read;
653 }
654
655 memcpy(&((char*)s)[bytes_read], stream->buffer, len);
656 bytes_read += len;
657 stream->buffer_left -= len;
658 return bytes_read;
659 }
660
661 /* Internal buffer already contains requested amount */
662 memcpy(s,
663 &stream->buffer[stream->buffer_fill - stream->buffer_left],
664 len);
665 stream->buffer_left -= len;
666 return len;
667 }
668
669 /* Big read exceeding buffer size,
670 * exhaust small buffer and read rest in one go */
671 memcpy(s, &stream->buffer[stream->buffer_fill - stream->buffer_left], stream->buffer_left);
672 len -= stream->buffer_left;
673 bytes_read += stream->buffer_left;
674 stream->buffer_left = 0;
675
676 buffer = CreateNativeBuffer(&((char*)s)[bytes_read], len, 0);
677 ret = RunAsyncAndCatchErrors<int64_t>([&]() {
678 return concurrency::create_task(stream->fp->ReadAsync(buffer, buffer->Capacity - bytes_read, InputStreamOptions::None)).then([&](IBuffer^ buf) {
679 retro_assert(buf == buffer);
680 return (int64_t)buffer->Length;
681 });
682 }, -1);
683
684 if (ret == -1)
685 return -1;
686 return bytes_read + ret;
687 }
688
retro_vfs_file_write_impl(libretro_vfs_implementation_file * stream,const void * s,uint64_t len)689 int64_t retro_vfs_file_write_impl(
690 libretro_vfs_implementation_file *stream, const void *s, uint64_t len)
691 {
692 IBuffer^ buffer;
693 if (!stream || (!stream->fp && stream->file_handle == INVALID_HANDLE_VALUE) || !s)
694 return -1;
695
696 if (stream->file_handle != INVALID_HANDLE_VALUE)
697 {
698 DWORD bytes_written;
699 WriteFile(stream->file_handle, s, len, &bytes_written, NULL);
700 return (int64_t)bytes_written;
701 }
702
703 /* const_cast to remove const modifier is undefined behaviour, but the buffer is only read, should be safe */
704 buffer = CreateNativeBuffer(const_cast<void*>(s), len, len);
705 return RunAsyncAndCatchErrors<int64_t>([&]() {
706 return concurrency::create_task(stream->fp->WriteAsync(buffer)).then([&](unsigned int written) {
707 return (int64_t)written;
708 });
709 }, -1);
710 }
711
retro_vfs_file_flush_impl(libretro_vfs_implementation_file * stream)712 int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream)
713 {
714 if (!stream || (!stream->fp && stream->file_handle == INVALID_HANDLE_VALUE) || !stream->fp)
715 return -1;
716
717 if (stream->file_handle != INVALID_HANDLE_VALUE)
718 {
719 FlushFileBuffers(stream->file_handle);
720 return 0;
721 }
722
723 return RunAsyncAndCatchErrors<int>([&]() {
724 return concurrency::create_task(stream->fp->FlushAsync()).then([&](bool this_value_is_not_even_documented_wtf) {
725 /* The bool value may be reporting an error or something, but just leave it alone for now */
726 /* https://github.com/MicrosoftDocs/winrt-api/issues/841 */
727 return 0;
728 });
729 }, -1);
730 }
731
retro_vfs_file_remove_impl(const char * path)732 int retro_vfs_file_remove_impl(const char *path)
733 {
734 BOOL result;
735 wchar_t *path_wide;
736 Platform::String^ path_str;
737
738 if (!path || !*path)
739 return -1;
740
741 path_wide = utf8_to_utf16_string_alloc(path);
742 windowsize_path(path_wide);
743 path_str = ref new Platform::String(path_wide);
744 free(path_wide);
745
746 /* Try Win32 first, this should work in AppData */
747 result = DeleteFileW(path_str->Data());
748 if (result)
749 return 0;
750
751 if (GetLastError() == ERROR_FILE_NOT_FOUND)
752 return -1;
753
754 /* Fallback to WinRT */
755 return RunAsyncAndCatchErrors<int>([&]() {
756 return concurrency::create_task(LocateStorageItem<StorageFile>(path_str)).then([&](StorageFile^ file) {
757 return file->DeleteAsync(StorageDeleteOption::PermanentDelete);
758 }).then([&]() {
759 return 0;
760 });
761 }, -1);
762 }
763
764 /* TODO: this may not work if trying to move a directory */
retro_vfs_file_rename_impl(const char * old_path,const char * new_path)765 int retro_vfs_file_rename_impl(const char *old_path, const char *new_path)
766 {
767 char new_file_name[PATH_MAX_LENGTH];
768 char new_dir_path[PATH_MAX_LENGTH];
769 wchar_t *new_file_name_wide;
770 wchar_t *old_path_wide, *new_dir_path_wide;
771 Platform::String^ old_path_str;
772 Platform::String^ new_dir_path_str;
773 Platform::String^ new_file_name_str;
774
775 if (!old_path || !*old_path || !new_path || !*new_path)
776 return -1;
777
778 new_file_name[0] = '\0';
779 new_dir_path [0] = '\0';
780
781 old_path_wide = utf8_to_utf16_string_alloc(old_path);
782 old_path_str = ref new Platform::String(old_path_wide);
783 free(old_path_wide);
784
785 fill_pathname_basedir(new_dir_path, new_path, sizeof(new_dir_path));
786 new_dir_path_wide = utf8_to_utf16_string_alloc(new_dir_path);
787 windowsize_path(new_dir_path_wide);
788 new_dir_path_str = ref new Platform::String(new_dir_path_wide);
789 free(new_dir_path_wide);
790
791 fill_pathname_base(new_file_name, new_path, sizeof(new_file_name));
792 new_file_name_wide = utf8_to_utf16_string_alloc(new_file_name);
793 new_file_name_str = ref new Platform::String(new_file_name_wide);
794 free(new_file_name_wide);
795
796 retro_assert(!old_path_str->IsEmpty() && !new_dir_path_str->IsEmpty() && !new_file_name_str->IsEmpty());
797
798 return RunAsyncAndCatchErrors<int>([&]() {
799 concurrency::task<StorageFile^> old_file_task = concurrency::create_task(LocateStorageItem<StorageFile>(old_path_str));
800 concurrency::task<StorageFolder^> new_dir_task = concurrency::create_task(LocateStorageItem<StorageFolder>(new_dir_path_str));
801 return concurrency::create_task([&] {
802 /* Run these two tasks in parallel */
803 /* TODO: There may be some cleaner way to express this */
804 concurrency::task_group group;
805 group.run([&] { return old_file_task; });
806 group.run([&] { return new_dir_task; });
807 group.wait();
808 }).then([&]() {
809 return old_file_task.get()->MoveAsync(new_dir_task.get(), new_file_name_str, NameCollisionOption::ReplaceExisting);
810 }).then([&]() {
811 return 0;
812 });
813 }, -1);
814 }
815
retro_vfs_file_get_path_impl(libretro_vfs_implementation_file * stream)816 const char *retro_vfs_file_get_path_impl(libretro_vfs_implementation_file *stream)
817 {
818 /* should never happen, do something noisy so caller can be fixed */
819 if (!stream)
820 abort();
821 return stream->orig_path;
822 }
823
retro_vfs_stat_impl(const char * path,int32_t * size)824 int retro_vfs_stat_impl(const char *path, int32_t *size)
825 {
826 wchar_t *path_wide;
827 Platform::String^ path_str;
828 IStorageItem^ item;
829 DWORD file_info;
830
831 if (!path || !*path)
832 return 0;
833
834 path_wide = utf8_to_utf16_string_alloc(path);
835 windowsize_path(path_wide);
836 path_str = ref new Platform::String(path_wide);
837 free(path_wide);
838
839 /* Try Win32 first, this should work in AppData */
840 file_info = GetFileAttributesW(path_str->Data());
841 if (file_info != INVALID_FILE_ATTRIBUTES)
842 {
843 HANDLE file_handle = CreateFile2(path_str->Data(), GENERIC_READ, FILE_SHARE_READ, OPEN_ALWAYS, NULL);
844 if (file_handle != INVALID_HANDLE_VALUE)
845 {
846 LARGE_INTEGER sz;
847 if (GetFileSizeEx(file_handle, &sz))
848 {
849 if (size)
850 *size = sz.QuadPart;
851 }
852 CloseHandle(file_handle);
853 }
854 return (file_info & FILE_ATTRIBUTE_DIRECTORY) ? RETRO_VFS_STAT_IS_VALID | RETRO_VFS_STAT_IS_DIRECTORY : RETRO_VFS_STAT_IS_VALID;
855 }
856
857 if (GetLastError() == ERROR_FILE_NOT_FOUND)
858 return 0;
859
860 /* Fallback to WinRT */
861 item = LocateStorageFileOrFolder(path_str);
862 if (!item)
863 return 0;
864
865 return RunAsyncAndCatchErrors<int>([&]() {
866 return concurrency::create_task(item->GetBasicPropertiesAsync()).then([&](BasicProperties^ properties) {
867 if (size)
868 *size = properties->Size;
869 return item->IsOfType(StorageItemTypes::Folder) ? RETRO_VFS_STAT_IS_VALID | RETRO_VFS_STAT_IS_DIRECTORY : RETRO_VFS_STAT_IS_VALID;
870 });
871 }, 0);
872 }
873
retro_vfs_mkdir_impl(const char * dir)874 int retro_vfs_mkdir_impl(const char *dir)
875 {
876 Platform::String^ parent_path_str;
877 Platform::String^ dir_name_str;
878 Platform::String^ dir_str;
879 wchar_t *dir_name_wide, *parent_path_wide, *dir_wide;
880 char *dir_local, *tmp;
881 char parent_path[PATH_MAX_LENGTH];
882 char dir_name[PATH_MAX_LENGTH];
883 BOOL result;
884
885 if (!dir || !*dir)
886 return -1;
887
888 dir_name[0] = '\0';
889
890 /* If the path ends with a slash, we have to remove
891 * it for basename to work */
892 dir_local = strdup(dir);
893 tmp = dir_local + strlen(dir_local) - 1;
894
895 if (PATH_CHAR_IS_SLASH(*tmp))
896 *tmp = 0;
897
898 dir_wide = utf8_to_utf16_string_alloc(dir_local);
899 windowsize_path(dir_wide);
900 dir_str = ref new Platform::String(dir_wide);
901 free(dir_wide);
902
903 fill_pathname_base(dir_name, dir_local, sizeof(dir_name));
904 dir_name_wide = utf8_to_utf16_string_alloc(dir_name);
905 dir_name_str = ref new Platform::String(dir_name_wide);
906 free(dir_name_wide);
907
908 fill_pathname_parent_dir(parent_path, dir_local, sizeof(parent_path));
909 parent_path_wide = utf8_to_utf16_string_alloc(parent_path);
910 windowsize_path(parent_path_wide);
911 parent_path_str = ref new Platform::String(parent_path_wide);
912 free(parent_path_wide);
913
914 retro_assert(!dir_name_str->IsEmpty()
915 && !parent_path_str->IsEmpty());
916
917 free(dir_local);
918
919 /* Try Win32 first, this should work in AppData */
920 result = CreateDirectoryW(dir_str->Data(), NULL);
921 if (result)
922 return 0;
923
924 if (GetLastError() == ERROR_ALREADY_EXISTS)
925 return -2;
926
927 /* Fallback to WinRT */
928 return RunAsyncAndCatchErrors<int>([&]() {
929 return concurrency::create_task(LocateStorageItem<StorageFolder>(
930 parent_path_str)).then([&](StorageFolder^ parent) {
931 return parent->CreateFolderAsync(dir_name_str);
932 }).then([&](concurrency::task<StorageFolder^> new_dir) {
933 try
934 {
935 new_dir.get();
936 }
937 catch (Platform::COMException^ e)
938 {
939 if (e->HResult == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))
940 return -2;
941 throw;
942 }
943 return 0;
944 });
945 }, -1);
946 }
947
948 #ifdef VFS_FRONTEND
949 struct retro_vfs_dir_handle
950 #else
951 struct libretro_vfs_implementation_dir
952 #endif
953 {
954 IVectorView<IStorageItem^>^ directory;
955 IIterator<IStorageItem^>^ entry;
956 char *entry_name;
957 };
958
retro_vfs_opendir_impl(const char * name,bool include_hidden)959 libretro_vfs_implementation_dir *retro_vfs_opendir_impl(const char *name, bool include_hidden)
960 {
961 wchar_t *name_wide;
962 Platform::String^ name_str;
963 libretro_vfs_implementation_dir *rdir;
964
965 if (!name || !*name)
966 return NULL;
967
968 rdir = (libretro_vfs_implementation_dir*)calloc(1, sizeof(*rdir));
969 if (!rdir)
970 return NULL;
971
972 name_wide = utf8_to_utf16_string_alloc(name);
973 windowsize_path(name_wide);
974 name_str = ref new Platform::String(name_wide);
975 free(name_wide);
976
977 rdir->directory = RunAsyncAndCatchErrors<IVectorView<IStorageItem^>^>([&]() {
978 return concurrency::create_task(LocateStorageItem<StorageFolder>(name_str)).then([&](StorageFolder^ folder) {
979 return folder->GetItemsAsync();
980 });
981 }, nullptr);
982
983 if (rdir->directory)
984 return rdir;
985
986 free(rdir);
987 return NULL;
988 }
989
retro_vfs_readdir_impl(libretro_vfs_implementation_dir * rdir)990 bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir *rdir)
991 {
992 if (!rdir->entry)
993 {
994 rdir->entry = rdir->directory->First();
995 return rdir->entry->HasCurrent;
996 }
997 return rdir->entry->MoveNext();
998 }
999
retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir * rdir)1000 const char *retro_vfs_dirent_get_name_impl(
1001 libretro_vfs_implementation_dir *rdir)
1002 {
1003 if (rdir->entry_name)
1004 free(rdir->entry_name);
1005 rdir->entry_name = utf16_to_utf8_string_alloc(
1006 rdir->entry->Current->Name->Data());
1007 return rdir->entry_name;
1008 }
1009
retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir * rdir)1010 bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir *rdir)
1011 {
1012 return rdir->entry->Current->IsOfType(StorageItemTypes::Folder);
1013 }
1014
retro_vfs_closedir_impl(libretro_vfs_implementation_dir * rdir)1015 int retro_vfs_closedir_impl(libretro_vfs_implementation_dir *rdir)
1016 {
1017 if (!rdir)
1018 return -1;
1019
1020 if (rdir->entry_name)
1021 free(rdir->entry_name);
1022 rdir->entry = nullptr;
1023 rdir->directory = nullptr;
1024
1025 free(rdir);
1026 return 0;
1027 }
1028
uwp_drive_exists(const char * path)1029 bool uwp_drive_exists(const char *path)
1030 {
1031 wchar_t *path_wide;
1032 Platform::String^ path_str;
1033 if (!path || !*path)
1034 return 0;
1035
1036 path_wide = utf8_to_utf16_string_alloc(path);
1037 path_str = ref new Platform::String(path_wide);
1038 free(path_wide);
1039
1040 return RunAsyncAndCatchErrors<bool>([&]() {
1041 return concurrency::create_task(StorageFolder::GetFolderFromPathAsync(path_str)).then([](StorageFolder^ properties) {
1042 return true;
1043 });
1044 }, false);
1045 }
1046
uwp_trigger_picker(void)1047 char* uwp_trigger_picker(void)
1048 {
1049 return RunAsyncAndCatchErrors<char*>([&]() {
1050 return TriggerPickerAddDialog().then([](Platform::String^ path) {
1051 return utf16_to_utf8_string_alloc(path->Data());
1052 });
1053 }, NULL);
1054 }
1055