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