(UWP) Base StorageFile implementation

This commit is contained in:
krzys-h 2019-01-06 14:49:26 +01:00
parent b70683fa08
commit b7cc124070
5 changed files with 624 additions and 0 deletions

View File

@ -926,7 +926,9 @@ FILE
#include "../libretro-common/streams/file_stream_transforms.c"
#include "../libretro-common/streams/interface_stream.c"
#include "../libretro-common/streams/memory_stream.c"
#ifndef __WINRT__
#include "../libretro-common/vfs/vfs_implementation.c"
#endif
#include "../list_special.c"
#include "../libretro-common/string/stdstring.c"
#include "../libretro-common/file/nbio/nbio_stdio.c"

View File

@ -44,6 +44,10 @@ typedef struct retro_vfs_dir_handle libretro_vfs_implementation_dir;
typedef struct libretro_vfs_implementation_dir libretro_vfs_implementation_dir;
#endif
#ifdef __cplusplus
extern "C" {
#endif
libretro_vfs_implementation_file *retro_vfs_file_open_impl(const char *path, unsigned mode, unsigned hints);
int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream);
@ -84,4 +88,8 @@ bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir *dirstream);
int retro_vfs_closedir_impl(libretro_vfs_implementation_dir *dirstream);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,605 @@
/* Copyright (C) 2018-2019 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (vfs_implementation_uwp.cpp).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <ppl.h>
#include <ppltasks.h>
#include <stdio.h>
#include <wrl.h>
#include <wrl/implements.h>
#include <windows.storage.streams.h>
#include <robuffer.h>
#include <functional>
using namespace Windows::Foundation::Collections;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;
using namespace Windows::Storage::FileProperties;
#ifdef RARCH_INTERNAL
#ifndef VFS_FRONTEND
#define VFS_FRONTEND
#endif
#endif
#include <vfs/vfs_implementation.h>
#include <libretro.h>
#include <encodings/utf.h>
#include <retro_miscellaneous.h>
#include <file/file_path.h>
#include <retro_assert.h>
#include <verbosity.h>
#include <string/stdstring.h>
#include <retro_environment.h>
namespace
{
/* Dear Microsoft
* I really appreciate all the effort you took to not provide any
* synchronous file APIs and block all attempts to synchronously
* wait for the results of async tasks for "smooth user experience",
* but I'm not going to run and rewrite all RetroArch cores for
* async I/O. I hope you like this hack I made instead.
*/
template<typename T>
T RunAsync(std::function<concurrency::task<T>()> func)
{
bool finished = false;
Platform::Exception^ exception = nullptr;
T result;
func().then([&](concurrency::task<T> t) {
try
{
result = t.get();
}
catch (Platform::Exception^ exception_)
{
exception = exception_;
}
finished = true;
});
while (!finished)
Windows::UI::Core::CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(Windows::UI::Core::CoreProcessEventsOption::ProcessAllIfPresent);
if (exception != nullptr)
throw exception;
return result;
}
template<typename T>
T RunAsyncAndCatchErrors(std::function<concurrency::task<T>()> func, T valueOnError)
{
try
{
return RunAsync<T>(func);
}
catch (Platform::Exception^ e)
{
return valueOnError;
}
}
void windowsize_path(wchar_t* path)
{
/* UWP deals with paths containing / instead of \ way worse than normal Windows */
/* and RetroArch may sometimes mix them (e.g. on archive extraction) */
if (!path)
return;
while (*path)
{
if (*path == '/')
*path = '\\';
++path;
}
}
}
#ifdef VFS_FRONTEND
struct retro_vfs_file_handle
#else
struct libretro_vfs_implementation_file
#endif
{
IRandomAccessStream^ fp;
char* orig_path;
};
libretro_vfs_implementation_file *retro_vfs_file_open_impl(const char *path, unsigned mode, unsigned hints)
{
if (!path || !*path)
return NULL;
if (!path_is_absolute(path))
{
RARCH_WARN("Something tried to access files from current directory ('%s'). This is not allowed on UWP.\n", path);
return NULL;
}
if (path_char_is_slash(path[strlen(path) - 1]))
{
RARCH_WARN("Trying to open a directory as file?! ('%s')\n", path);
return NULL;
}
char* dirpath = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
fill_pathname_basedir(dirpath, path, PATH_MAX_LENGTH);
wchar_t *dirpath_wide = utf8_to_utf16_string_alloc(dirpath);
windowsize_path(dirpath_wide);
Platform::String^ dirpath_str = ref new Platform::String(dirpath_wide);
free(dirpath_wide);
free(dirpath);
char* filename = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
fill_pathname_base(filename, path, PATH_MAX_LENGTH);
wchar_t *filename_wide = utf8_to_utf16_string_alloc(filename);
Platform::String^ filename_str = ref new Platform::String(filename_wide);
free(filename_wide);
free(filename);
retro_assert(!dirpath_str->IsEmpty() && !filename_str->IsEmpty());
return RunAsyncAndCatchErrors<libretro_vfs_implementation_file*>([&]() {
return concurrency::create_task(StorageFolder::GetFolderFromPathAsync(dirpath_str)).then([&](StorageFolder^ dir) {
if (mode == RETRO_VFS_FILE_ACCESS_READ)
return dir->GetFileAsync(filename_str);
else
return dir->CreateFileAsync(filename_str, (mode & RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING) != 0 ?
CreationCollisionOption::OpenIfExists : CreationCollisionOption::ReplaceExisting);
}).then([&](StorageFile^ file) {
FileAccessMode accessMode = mode == RETRO_VFS_FILE_ACCESS_READ ?
FileAccessMode::Read : FileAccessMode::ReadWrite;
return file->OpenAsync(accessMode);
}).then([&](IRandomAccessStream^ fpstream) {
libretro_vfs_implementation_file *stream = (libretro_vfs_implementation_file*)calloc(1, sizeof(*stream));
if (!stream)
return (libretro_vfs_implementation_file*)NULL;
stream->orig_path = strdup(path);
stream->fp = fpstream;
stream->fp->Seek(0);
return stream;
});
}, NULL);
}
int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream)
{
if (!stream || !stream->fp)
return -1;
/* Apparently, this is how you close a file in WinRT */
/* Yes, really */
stream->fp = nullptr;
return 0;
}
int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream)
{
return false; /* TODO */
}
int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file *stream)
{
if (!stream || !stream->fp)
return 0;
return stream->fp->Size;
}
int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, int64_t length)
{
if (!stream || !stream->fp)
return -1;
stream->fp->Size = length;
return 0;
}
int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream)
{
if (!stream || !stream->fp)
return -1;
return stream->fp->Position;
}
int64_t retro_vfs_file_seek_impl(libretro_vfs_implementation_file *stream, int64_t offset, int seek_position)
{
if (!stream || !stream->fp)
return -1;
switch (seek_position)
{
case RETRO_VFS_SEEK_POSITION_START:
stream->fp->Seek(offset);
break;
case RETRO_VFS_SEEK_POSITION_CURRENT:
stream->fp->Seek(stream->fp->Position + offset);
break;
case RETRO_VFS_SEEK_POSITION_END:
stream->fp->Seek(stream->fp->Size - offset);
break;
}
return 0;
}
/* This is some pure magic and I have absolutely no idea how it works */
/* Wraps a raw buffer into a WinRT object */
/* https://stackoverflow.com/questions/10520335/how-to-wrap-a-char-buffer-in-a-winrt-ibuffer-in-c */
class NativeBuffer :
public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::WinRtClassicComMix>,
ABI::Windows::Storage::Streams::IBuffer,
Windows::Storage::Streams::IBufferByteAccess>
{
public:
virtual ~NativeBuffer()
{
}
HRESULT __stdcall RuntimeClassInitialize(byte *buffer, uint32_t capacity, uint32_t length)
{
m_buffer = buffer;
m_capacity = capacity;
m_length = length;
return S_OK;
}
HRESULT __stdcall Buffer(byte **value)
{
if (m_buffer == nullptr)
return E_INVALIDARG;
*value = m_buffer;
return S_OK;
}
HRESULT __stdcall get_Capacity(uint32_t *value)
{
*value = m_capacity;
return S_OK;
}
HRESULT __stdcall get_Length(uint32_t *value)
{
*value = m_length;
return S_OK;
}
HRESULT __stdcall put_Length(uint32_t value)
{
if (value > m_capacity)
return E_INVALIDARG;
m_length = value;
return S_OK;
}
private:
byte *m_buffer;
uint32_t m_capacity;
uint32_t m_length;
};
IBuffer^ CreateNativeBuffer(void* buf, uint32_t capacity, uint32_t length)
{
Microsoft::WRL::ComPtr<NativeBuffer> nativeBuffer;
Microsoft::WRL::Details::MakeAndInitialize<NativeBuffer>(&nativeBuffer, (byte *)buf, capacity, length);
auto iinspectable = (IInspectable *)reinterpret_cast<IInspectable *>(nativeBuffer.Get());
IBuffer ^buffer = reinterpret_cast<IBuffer ^>(iinspectable);
return buffer;
}
int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream, void *s, uint64_t len)
{
if (!stream || !stream->fp || !s)
return -1;
IBuffer^ buffer = CreateNativeBuffer(s, len, 0);
return RunAsyncAndCatchErrors<int64_t>([&]() {
return concurrency::create_task(stream->fp->ReadAsync(buffer, buffer->Capacity, InputStreamOptions::None)).then([&](IBuffer^ buf) {
retro_assert(buf == buffer);
return (int64_t)buffer->Length;
});
}, -1);
}
int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, const void *s, uint64_t len)
{
if (!stream || !stream->fp || !s)
return -1;
IBuffer^ buffer = CreateNativeBuffer(const_cast<void*>(s), len, len);
return RunAsyncAndCatchErrors<int64_t>([&]() {
return concurrency::create_task(stream->fp->WriteAsync(buffer)).then([&](unsigned int written) {
return (int64_t)written;
});
}, -1);
}
int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream)
{
if (!stream || !stream->fp)
return -1;
return RunAsyncAndCatchErrors<int>([&]() {
return concurrency::create_task(stream->fp->FlushAsync()).then([&](bool this_value_is_not_even_documented_wtf) {
/* The bool value may be reporting an error or something, but just leave it alone for now */
/* https://github.com/MicrosoftDocs/winrt-api/issues/841 */
return 0;
});
}, -1);
}
int retro_vfs_file_remove_impl(const char *path)
{
if (!path || !*path)
return -1;
wchar_t *path_wide = utf8_to_utf16_string_alloc(path);
windowsize_path(path_wide);
Platform::String^ path_str = ref new Platform::String(path_wide);
free(path_wide);
return RunAsyncAndCatchErrors<int>([&]() {
return concurrency::create_task(StorageFile::GetFileFromPathAsync(path_str)).then([&](StorageFile^ file) {
return file->DeleteAsync(StorageDeleteOption::PermanentDelete);
}).then([&]() {
return 0;
});
}, -1);
}
/* TODO: this may not work if trying to move a directory */
int retro_vfs_file_rename_impl(const char *old_path, const char *new_path)
{
if (!old_path || !*old_path || !new_path || !*new_path)
return -1;
wchar_t* old_path_wide = utf8_to_utf16_string_alloc(old_path);
Platform::String^ old_path_str = ref new Platform::String(old_path_wide);
free(old_path_wide);
char* new_dir_path = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
fill_pathname_basedir(new_dir_path, new_path, PATH_MAX_LENGTH);
wchar_t *new_dir_path_wide = utf8_to_utf16_string_alloc(new_dir_path);
windowsize_path(new_dir_path_wide);
Platform::String^ new_dir_path_str = ref new Platform::String(new_dir_path_wide);
free(new_dir_path_wide);
free(new_dir_path);
char* new_file_name = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
fill_pathname_base(new_file_name, new_path, PATH_MAX_LENGTH);
wchar_t *new_file_name_wide = utf8_to_utf16_string_alloc(new_file_name);
Platform::String^ new_file_name_str = ref new Platform::String(new_file_name_wide);
free(new_file_name_wide);
free(new_file_name);
retro_assert(!old_path_str->IsEmpty() && !new_dir_path_str->IsEmpty() && !new_file_name_str->IsEmpty());
return RunAsyncAndCatchErrors<int>([&]() {
concurrency::task<StorageFile^> old_file_task = concurrency::create_task(StorageFile::GetFileFromPathAsync(old_path_str));
concurrency::task<StorageFolder^> new_dir_task = concurrency::create_task(StorageFolder::GetFolderFromPathAsync(new_dir_path_str));
return concurrency::create_task([&] {
/* Run these two tasks in parallel */
/* TODO: There may be some cleaner way to express this */
concurrency::task_group group;
group.run([&] { return old_file_task; });
group.run([&] { return new_dir_task; });
group.wait();
}).then([&]() {
return old_file_task.get()->MoveAsync(new_dir_task.get(), new_file_name_str, NameCollisionOption::ReplaceExisting);
}).then([&]() {
return 0;
});
}, -1);
}
const char *retro_vfs_file_get_path_impl(libretro_vfs_implementation_file *stream)
{
/* should never happen, do something noisy so caller can be fixed */
if (!stream)
abort();
return stream->orig_path;
}
/* This is ugly, but I can't figure out a better way and there may be no better way... */
static IStorageItem^ GetFileOrFolderFromPath(Platform::String^ path)
{
if (!path || path->IsEmpty())
return nullptr;
if (*(path->End() - 1) == '\\')
{
/* Ends with a slash, so it's definitely a directory */
return RunAsyncAndCatchErrors<StorageFolder^>([&]() {
return concurrency::create_task(StorageFolder::GetFolderFromPathAsync(path));
}, nullptr);
}
else
{
/* No final slash - probably a file (since RetroArch usually slash-terminates dirs), but there is a chance it's a directory */
IStorageItem^ item;
item = RunAsyncAndCatchErrors<StorageFile^>([&]() {
return concurrency::create_task(StorageFile::GetFileFromPathAsync(path));
}, nullptr);
if (!item)
{
item = RunAsyncAndCatchErrors<StorageFolder^>([&]() {
return concurrency::create_task(StorageFolder::GetFolderFromPathAsync(path));
}, nullptr);
}
return item;
}
}
int retro_vfs_stat_impl(const char *path, int32_t *size)
{
if (!path || !*path)
return 0;
wchar_t *path_wide = utf8_to_utf16_string_alloc(path);
windowsize_path(path_wide);
Platform::String^ path_str = ref new Platform::String(path_wide);
free(path_wide);
IStorageItem^ item = GetFileOrFolderFromPath(path_str);
if (!item)
return 0;
return RunAsyncAndCatchErrors<int>([&]() {
return concurrency::create_task(item->GetBasicPropertiesAsync()).then([&](BasicProperties^ properties) {
if (size)
*size = properties->Size;
return item->IsOfType(StorageItemTypes::Folder) ? RETRO_VFS_STAT_IS_VALID | RETRO_VFS_STAT_IS_DIRECTORY : RETRO_VFS_STAT_IS_VALID;
});
}, 0);
}
int retro_vfs_mkdir_impl(const char *dir)
{
if (!dir || !*dir)
return -1;
char* dir_local = strdup(dir);
/* If the path ends with a slash, we have to remove it for basename to work */
char* tmp = dir_local + strlen(dir_local) - 1;
if (path_char_is_slash(*tmp))
*tmp = 0;
char* dir_name = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
fill_pathname_base(dir_name, dir_local, PATH_MAX_LENGTH);
wchar_t *dir_name_wide = utf8_to_utf16_string_alloc(dir_name);
Platform::String^ dir_name_str = ref new Platform::String(dir_name_wide);
free(dir_name_wide);
free(dir_name);
char* parent_path = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
fill_pathname_parent_dir(parent_path, dir_local, PATH_MAX_LENGTH);
wchar_t *parent_path_wide = utf8_to_utf16_string_alloc(parent_path);
windowsize_path(parent_path_wide);
Platform::String^ parent_path_str = ref new Platform::String(parent_path_wide);
free(parent_path_wide);
free(parent_path);
retro_assert(!dir_name_str->IsEmpty() && !parent_path_str->IsEmpty());
free(dir_local);
return RunAsyncAndCatchErrors<int>([&]() {
return concurrency::create_task(StorageFolder::GetFolderFromPathAsync(parent_path_str)).then([&](StorageFolder^ parent) {
return parent->CreateFolderAsync(dir_name_str);
}).then([&](concurrency::task<StorageFolder^> new_dir) {
try
{
new_dir.get();
}
catch (Platform::COMException^ e)
{
if (e->HResult == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))
return -2;
throw;
}
return 0;
});
}, -1);
}
#ifdef VFS_FRONTEND
struct retro_vfs_dir_handle
#else
struct libretro_vfs_implementation_dir
#endif
{
IVectorView<IStorageItem^>^ directory;
IIterator<IStorageItem^>^ entry;
char *entry_name;
};
libretro_vfs_implementation_dir *retro_vfs_opendir_impl(const char *name, bool include_hidden)
{
libretro_vfs_implementation_dir *rdir;
if (!name || !*name)
return NULL;
rdir = (libretro_vfs_implementation_dir*)calloc(1, sizeof(*rdir));
if (!rdir)
return NULL;
wchar_t *name_wide = utf8_to_utf16_string_alloc(name);
windowsize_path(name_wide);
Platform::String^ name_str = ref new Platform::String(name_wide);
free(name_wide);
rdir->directory = RunAsyncAndCatchErrors<IVectorView<IStorageItem^>^>([&]() {
return concurrency::create_task(StorageFolder::GetFolderFromPathAsync(name_str)).then([&](StorageFolder^ folder) {
return folder->GetItemsAsync();
});
}, nullptr);
if (rdir->directory)
return rdir;
free(rdir);
return NULL;
}
bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir *rdir)
{
if (!rdir->entry)
{
rdir->entry = rdir->directory->First();
return rdir->entry->HasCurrent;
}
else
{
return rdir->entry->MoveNext();
}
}
const char *retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir *rdir)
{
if (rdir->entry_name)
free(rdir->entry_name);
rdir->entry_name = utf16_to_utf8_string_alloc(rdir->entry->Current->Name->Data());
return rdir->entry_name;
}
bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir *rdir)
{
return rdir->entry->Current->IsOfType(StorageItemTypes::Folder);
}
int retro_vfs_closedir_impl(libretro_vfs_implementation_dir *rdir)
{
if (!rdir)
return -1;
if (rdir->entry_name)
free(rdir->entry_name);
rdir->entry = nullptr;
rdir->directory = nullptr;
free(rdir);
return 0;
}

View File

@ -282,6 +282,9 @@
</AppxManifest>
<None Include="RetroArch-UWP_TemporaryKey.pfx" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\libretro-common\vfs\vfs_implementation_uwp.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\uwp\uwp_func.h" />
<ClInclude Include="..\..\..\uwp\uwp_main.h" />

View File

@ -10,6 +10,9 @@
<Filter Include="Assets">
<UniqueIdentifier>{c3155604-6d38-494a-bfe0-861cef871cb2}</UniqueIdentifier>
</Filter>
<Filter Include="libretro-common-uwp">
<UniqueIdentifier>{d41660c5-7f5b-442c-b5d7-03e6e9af8172}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest" />
@ -40,6 +43,9 @@
<ClCompile Include="..\..\..\griffin\griffin_glslang.cpp">
<Filter>griffin</Filter>
</ClCompile>
<ClCompile Include="..\..\..\libretro-common\vfs\vfs_implementation_uwp.cpp">
<Filter>libretro-common-uwp</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Image Include="Assets\SmallTile.scale-100.png">