mirror of
https://github.com/libretro/RetroArch
synced 2025-03-02 19:13:34 +00:00
Merge pull request #8025 from krzys-h/uwp-storagefile
(UWP) StorageFile implementation
This commit is contained in:
commit
556b7b19de
2
core.h
2
core.h
@ -65,6 +65,8 @@ typedef struct rarch_system_info
|
||||
const char *input_desc_btn[MAX_USERS][RARCH_FIRST_META_KEY];
|
||||
char valid_extensions[255];
|
||||
|
||||
bool supports_vfs;
|
||||
|
||||
struct retro_disk_control_callback disk_control_cb;
|
||||
struct retro_location_callback location_cb;
|
||||
|
||||
|
@ -1959,6 +1959,7 @@ bool rarch_environment_cb(unsigned cmd, void *data)
|
||||
RARCH_LOG("Core requested VFS version >= v%d, providing v%d\n", vfs_iface_info->required_interface_version, supported_vfs_version);
|
||||
vfs_iface_info->required_interface_version = supported_vfs_version;
|
||||
vfs_iface_info->iface = &vfs_iface;
|
||||
system->supports_vfs = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -271,18 +271,48 @@ static int frontend_uwp_parse_drive_list(void *data, bool load_content)
|
||||
enum msg_hash_enums enum_idx = load_content ?
|
||||
MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR :
|
||||
MSG_UNKNOWN;
|
||||
/* TODO (krzys_h): UWP storage sandboxing */
|
||||
char *home_dir = (char*)malloc(
|
||||
PATH_MAX_LENGTH * sizeof(char));
|
||||
char drive[] = " :\\";
|
||||
char *home_dir = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
|
||||
bool have_any_drives = false;
|
||||
|
||||
fill_pathname_home_dir(home_dir,
|
||||
PATH_MAX_LENGTH * sizeof(char));
|
||||
fill_pathname_home_dir(home_dir, PATH_MAX_LENGTH * sizeof(char));
|
||||
|
||||
for (drive[0] = 'A'; drive[0] <= 'Z'; drive[0]++)
|
||||
{
|
||||
if (uwp_drive_exists(drive))
|
||||
{
|
||||
menu_entries_append_enum(list,
|
||||
drive,
|
||||
msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
|
||||
enum_idx,
|
||||
FILE_TYPE_DIRECTORY, 0, 0);
|
||||
have_any_drives = true;
|
||||
}
|
||||
}
|
||||
|
||||
menu_entries_append_enum(list,
|
||||
home_dir,
|
||||
msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
|
||||
enum_idx,
|
||||
FILE_TYPE_DIRECTORY, 0, 0);
|
||||
home_dir,
|
||||
msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
|
||||
enum_idx,
|
||||
FILE_TYPE_DIRECTORY, 0, 0);
|
||||
|
||||
if (!have_any_drives)
|
||||
{
|
||||
menu_entries_append_enum(list,
|
||||
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FILE_BROWSER_OPEN_PICKER),
|
||||
msg_hash_to_str(MENU_ENUM_LABEL_FILE_BROWSER_OPEN_PICKER),
|
||||
MENU_ENUM_LABEL_FILE_BROWSER_OPEN_PICKER,
|
||||
MENU_SETTING_ACTION, 0, 0);
|
||||
|
||||
if (string_is_equal(uwp_device_family, "Windows.Desktop"))
|
||||
{
|
||||
menu_entries_append_enum(list,
|
||||
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FILE_BROWSER_OPEN_UWP_PERMISSIONS),
|
||||
msg_hash_to_str(MENU_ENUM_LABEL_FILE_BROWSER_OPEN_UWP_PERMISSIONS),
|
||||
MENU_ENUM_LABEL_FILE_BROWSER_OPEN_UWP_PERMISSIONS,
|
||||
MENU_SETTING_ACTION, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
free(home_dir);
|
||||
#endif
|
||||
|
@ -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"
|
||||
|
@ -390,6 +390,16 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
|
||||
snprintf(s, len,
|
||||
"Go back to the parent directory.");
|
||||
break;
|
||||
case MENU_ENUM_LABEL_FILE_BROWSER_OPEN_UWP_PERMISSIONS:
|
||||
snprintf(s, len,
|
||||
"Open Windows permission settings to enable \n"
|
||||
"the broadFileSystemAccess capability.");
|
||||
break;
|
||||
case MENU_ENUM_LABEL_FILE_BROWSER_OPEN_PICKER:
|
||||
snprintf(s, len,
|
||||
"Open the system file picker to access \n"
|
||||
"additional directories.");
|
||||
break;
|
||||
case MENU_ENUM_LABEL_FILE_BROWSER_SHADER_PRESET:
|
||||
snprintf(s, len,
|
||||
"Shader preset file.");
|
||||
|
@ -2044,6 +2044,22 @@ MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_PARENT_DIRECTORY,
|
||||
"Parent directory"
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_FILE_BROWSER_OPEN_UWP_PERMISSIONS,
|
||||
"Enable external file access"
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_SUBLABEL_FILE_BROWSER_OPEN_UWP_PERMISSIONS,
|
||||
"Open Windows file access permissions settings"
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_FILE_BROWSER_OPEN_PICKER,
|
||||
"Open..."
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_SUBLABEL_FILE_BROWSER_OPEN_PICKER,
|
||||
"Open another directory using the system file picker"
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_PAUSE_LIBRETRO,
|
||||
"Pause when menu activated"
|
||||
@ -4151,6 +4167,10 @@ MSG_HASH(
|
||||
MSG_ERROR_LIBRETRO_CORE_REQUIRES_SPECIAL_CONTENT,
|
||||
"Libretro core requires special content, but none were provided."
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_ERROR_LIBRETRO_CORE_REQUIRES_VFS,
|
||||
"Core does not support VFS, and loading from a local copy failed"
|
||||
)
|
||||
MSG_HASH(
|
||||
MSG_ERROR_PARSING_ARGUMENTS,
|
||||
"Error parsing arguments."
|
||||
|
@ -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
|
||||
|
758
libretro-common/vfs/vfs_implementation_uwp.cpp
Normal file
758
libretro-common/vfs/vfs_implementation_uwp.cpp
Normal file
@ -0,0 +1,758 @@
|
||||
/* 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 <collection.h>
|
||||
#include <functional>
|
||||
|
||||
using namespace Windows::Foundation;
|
||||
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>
|
||||
#include <uwp/uwp_func.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)
|
||||
{
|
||||
volatile bool finished = false;
|
||||
volatile Platform::Exception^ exception = nullptr;
|
||||
volatile T result;
|
||||
func().then([&finished, &exception, &result](concurrency::task<T> t) {
|
||||
try
|
||||
{
|
||||
result = t.get();
|
||||
}
|
||||
catch (Platform::Exception^ exception_)
|
||||
{
|
||||
exception = exception_;
|
||||
}
|
||||
finished = true;
|
||||
});
|
||||
|
||||
/* Don't stall the UI thread - prevents a deadlock */
|
||||
Windows::UI::Core::CoreWindow^ corewindow = Windows::UI::Core::CoreWindow::GetForCurrentThread();
|
||||
while (!finished)
|
||||
{
|
||||
if (corewindow)
|
||||
corewindow->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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
/* Damn you, UWP, why no functions for that either */
|
||||
template<typename T>
|
||||
concurrency::task<T^> GetItemFromPathAsync(Platform::String^ path)
|
||||
{
|
||||
static_assert(false, "StorageFile and StorageFolder only");
|
||||
}
|
||||
template<>
|
||||
concurrency::task<StorageFile^> GetItemFromPathAsync(Platform::String^ path)
|
||||
{
|
||||
return concurrency::create_task(StorageFile::GetFileFromPathAsync(path));
|
||||
}
|
||||
template<>
|
||||
concurrency::task<StorageFolder^> GetItemFromPathAsync(Platform::String^ path)
|
||||
{
|
||||
return concurrency::create_task(StorageFolder::GetFolderFromPathAsync(path));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
concurrency::task<T^> GetItemInFolderFromPathAsync(StorageFolder^ folder, Platform::String^ path)
|
||||
{
|
||||
static_assert(false, "StorageFile and StorageFolder only");
|
||||
}
|
||||
template<>
|
||||
concurrency::task<StorageFile^> GetItemInFolderFromPathAsync(StorageFolder^ folder, Platform::String^ path)
|
||||
{
|
||||
if (path->IsEmpty())
|
||||
retro_assert(false); /* Attempt to read a folder as a file - this really should have been caught earlier */
|
||||
return concurrency::create_task(folder->GetFileAsync(path));
|
||||
}
|
||||
template<>
|
||||
concurrency::task<StorageFolder^> GetItemInFolderFromPathAsync(StorageFolder^ folder, Platform::String^ path)
|
||||
{
|
||||
if (path->IsEmpty())
|
||||
return concurrency::create_task(concurrency::create_async([folder]() { return folder; }));
|
||||
return concurrency::create_task(folder->GetFolderAsync(path));
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
/* A list of all StorageFolder objects returned using from the file picker */
|
||||
Platform::Collections::Vector<StorageFolder^> accessible_directories;
|
||||
|
||||
concurrency::task<Platform::String^> TriggerPickerAddDialog()
|
||||
{
|
||||
auto folderPicker = ref new Windows::Storage::Pickers::FolderPicker();
|
||||
folderPicker->SuggestedStartLocation = Windows::Storage::Pickers::PickerLocationId::Desktop;
|
||||
folderPicker->FileTypeFilter->Append("*");
|
||||
|
||||
return concurrency::create_task(folderPicker->PickSingleFolderAsync()).then([](StorageFolder^ folder) {
|
||||
if (folder == nullptr)
|
||||
throw ref new Platform::Exception(E_ABORT, L"Operation cancelled by user");
|
||||
|
||||
/* TODO: check for duplicates */
|
||||
accessible_directories.Append(folder);
|
||||
return folder->Path;
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
concurrency::task<T^> LocateStorageItem(Platform::String^ path)
|
||||
{
|
||||
/* Look for a matching directory we can use */
|
||||
for each (StorageFolder^ folder in accessible_directories)
|
||||
{
|
||||
std::wstring folder_path = folder->Path->Data();
|
||||
/* Could be C:\ or C:\Users\somebody - remove the trailing slash to unify them */
|
||||
if (folder_path[folder_path.size() - 1] == '\\')
|
||||
folder_path.erase(folder_path.size() - 1);
|
||||
std::wstring file_path = path->Data();
|
||||
if (file_path.find(folder_path) == 0)
|
||||
{
|
||||
/* Found a match */
|
||||
file_path = file_path.length() > folder_path.length() ? file_path.substr(folder_path.length() + 1) : L"";
|
||||
return concurrency::create_task(GetItemInFolderFromPathAsync<T>(folder, ref new Platform::String(file_path.data())));
|
||||
}
|
||||
}
|
||||
|
||||
/* No matches - try accessing directly, and fallback to user prompt */
|
||||
return concurrency::create_task(GetItemFromPathAsync<T>(path)).then([path](concurrency::task<T^> item) {
|
||||
try
|
||||
{
|
||||
T^ storageItem = item.get();
|
||||
return concurrency::create_task(concurrency::create_async([storageItem]() { return storageItem; }));
|
||||
}
|
||||
catch (Platform::AccessDeniedException^ e)
|
||||
{
|
||||
Windows::UI::Popups::MessageDialog^ dialog =
|
||||
ref new Windows::UI::Popups::MessageDialog("Path \"" + path + "\" is not currently accessible. Please open any containing directory to access it.");
|
||||
dialog->Commands->Append(ref new Windows::UI::Popups::UICommand("Open file picker"));
|
||||
dialog->Commands->Append(ref new Windows::UI::Popups::UICommand("Cancel"));
|
||||
return concurrency::create_task(dialog->ShowAsync()).then([path](Windows::UI::Popups::IUICommand^ cmd) {
|
||||
if (cmd->Label == "Open file picker")
|
||||
{
|
||||
return TriggerPickerAddDialog().then([path](Platform::String^ added_path) {
|
||||
/* Retry */
|
||||
return LocateStorageItem<T>(path);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ref new Platform::Exception(E_ABORT, L"Operation cancelled by user");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
IStorageItem^ LocateStorageFileOrFolder(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(LocateStorageItem<StorageFolder>(path));
|
||||
}, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* No final slash - probably a file (since RetroArch usually slash-terminates dirs), but there is still a chance it's a directory */
|
||||
IStorageItem^ item;
|
||||
item = RunAsyncAndCatchErrors<StorageFile^>([=]() {
|
||||
return concurrency::create_task(LocateStorageItem<StorageFile>(path));
|
||||
}, nullptr);
|
||||
if (!item)
|
||||
{
|
||||
item = RunAsyncAndCatchErrors<StorageFolder^>([=]() {
|
||||
return concurrency::create_task(LocateStorageItem<StorageFolder>(path));
|
||||
}, nullptr);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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(LocateStorageItem<StorageFolder>(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(LocateStorageItem<StorageFile>(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(LocateStorageItem<StorageFile>(old_path_str));
|
||||
concurrency::task<StorageFolder^> new_dir_task = concurrency::create_task(LocateStorageItem<StorageFolder>(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;
|
||||
}
|
||||
|
||||
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 = LocateStorageFileOrFolder(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(LocateStorageItem<StorageFolder>(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(LocateStorageItem<StorageFolder>(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;
|
||||
}
|
||||
|
||||
bool uwp_is_path_accessible_using_standard_io(char *path)
|
||||
{
|
||||
char *relative_path_abbrev = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
|
||||
fill_pathname_abbreviate_special(relative_path_abbrev, path, PATH_MAX_LENGTH * sizeof(char));
|
||||
|
||||
bool result = strlen(relative_path_abbrev) >= 2 && (relative_path_abbrev[0] == ':' || relative_path_abbrev[0] == '~') && path_char_is_slash(relative_path_abbrev[1]);
|
||||
|
||||
free(relative_path_abbrev);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool uwp_drive_exists(const char *path)
|
||||
{
|
||||
if (!path || !*path)
|
||||
return 0;
|
||||
|
||||
wchar_t *path_wide = utf8_to_utf16_string_alloc(path);
|
||||
Platform::String^ path_str = ref new Platform::String(path_wide);
|
||||
free(path_wide);
|
||||
|
||||
return RunAsyncAndCatchErrors<bool>([=]() {
|
||||
return concurrency::create_task(StorageFolder::GetFolderFromPathAsync(path_str)).then([](StorageFolder^ properties) {
|
||||
return true;
|
||||
});
|
||||
}, false);
|
||||
}
|
||||
|
||||
char* uwp_trigger_picker(void)
|
||||
{
|
||||
return RunAsyncAndCatchErrors<char*>([=]() {
|
||||
return TriggerPickerAddDialog().then([](Platform::String^ path) {
|
||||
return utf16_to_utf8_string_alloc(path->Data());
|
||||
});
|
||||
}, NULL);
|
||||
}
|
@ -81,6 +81,10 @@
|
||||
|
||||
#include "../../record/record_driver.h"
|
||||
|
||||
#ifdef __WINRT__
|
||||
#include "../../uwp/uwp_func.h"
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
ACTION_OK_LOAD_PRESET = 0,
|
||||
@ -3976,6 +3980,39 @@ default_action_ok_func(action_ok_push_accounts_youtube_list, ACTION_OK_DL_ACCOUN
|
||||
default_action_ok_func(action_ok_push_accounts_twitch_list, ACTION_OK_DL_ACCOUNTS_TWITCH_LIST)
|
||||
default_action_ok_func(action_ok_open_archive, ACTION_OK_DL_OPEN_ARCHIVE)
|
||||
|
||||
static int action_ok_open_uwp_permission_settings(const char *path,
|
||||
const char *label, unsigned type, size_t idx, size_t entry_idx)
|
||||
{
|
||||
#ifdef __WINRT__
|
||||
uwp_open_broadfilesystemaccess_settings();
|
||||
#else
|
||||
retro_assert(false);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int action_ok_open_picker(const char *path,
|
||||
const char *label, unsigned type, size_t idx, size_t entry_idx)
|
||||
{
|
||||
char* new_path;
|
||||
int ret;
|
||||
#ifdef __WINRT__
|
||||
new_path = uwp_trigger_picker();
|
||||
if (!new_path)
|
||||
return 0; /* User aborted */
|
||||
#else
|
||||
retro_assert(false);
|
||||
#endif
|
||||
|
||||
ret = generic_action_ok_displaylist_push(path, new_path,
|
||||
msg_hash_to_str(MENU_ENUM_LABEL_FAVORITES),
|
||||
type, idx,
|
||||
entry_idx, ACTION_OK_DL_CONTENT_LIST);
|
||||
|
||||
free(new_path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int action_ok_shader_pass(const char *path,
|
||||
const char *label, unsigned type, size_t idx, size_t entry_idx)
|
||||
{
|
||||
@ -5476,6 +5513,12 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs,
|
||||
case MENU_ENUM_LABEL_MENU_FILE_BROWSER_SETTINGS:
|
||||
BIND_ACTION_OK(cbs, action_ok_menu_file_browser_list);
|
||||
break;
|
||||
case MENU_ENUM_LABEL_FILE_BROWSER_OPEN_UWP_PERMISSIONS:
|
||||
BIND_ACTION_OK(cbs, action_ok_open_uwp_permission_settings);
|
||||
break;
|
||||
case MENU_ENUM_LABEL_FILE_BROWSER_OPEN_PICKER:
|
||||
BIND_ACTION_OK(cbs, action_ok_open_picker);
|
||||
break;
|
||||
case MENU_ENUM_LABEL_RETRO_ACHIEVEMENTS_SETTINGS:
|
||||
BIND_ACTION_OK(cbs, action_ok_retro_achievements_list);
|
||||
break;
|
||||
|
@ -334,6 +334,8 @@ default_sublabel_macro(action_bind_sublabel_goto_images,
|
||||
default_sublabel_macro(action_bind_sublabel_goto_music, MENU_ENUM_SUBLABEL_GOTO_MUSIC)
|
||||
default_sublabel_macro(action_bind_sublabel_goto_video, MENU_ENUM_SUBLABEL_GOTO_VIDEO)
|
||||
default_sublabel_macro(action_bind_sublabel_menu_filebrowser_settings, MENU_ENUM_SUBLABEL_MENU_FILE_BROWSER_SETTINGS)
|
||||
default_sublabel_macro(action_bind_sublabel_menu_filebrowser_open_uwp_permissions, MENU_ENUM_SUBLABEL_FILE_BROWSER_OPEN_UWP_PERMISSIONS)
|
||||
default_sublabel_macro(action_bind_sublabel_menu_filebrowser_open_picker, MENU_ENUM_SUBLABEL_FILE_BROWSER_OPEN_PICKER)
|
||||
default_sublabel_macro(action_bind_sublabel_auto_remaps_enable, MENU_ENUM_SUBLABEL_AUTO_REMAPS_ENABLE)
|
||||
default_sublabel_macro(action_bind_sublabel_auto_overrides_enable, MENU_ENUM_SUBLABEL_AUTO_OVERRIDES_ENABLE)
|
||||
default_sublabel_macro(action_bind_sublabel_game_specific_options, MENU_ENUM_SUBLABEL_GAME_SPECIFIC_OPTIONS)
|
||||
@ -1379,6 +1381,12 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
|
||||
case MENU_ENUM_LABEL_MENU_FILE_BROWSER_SETTINGS:
|
||||
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_filebrowser_settings);
|
||||
break;
|
||||
case MENU_ENUM_LABEL_FILE_BROWSER_OPEN_UWP_PERMISSIONS:
|
||||
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_filebrowser_open_uwp_permissions);
|
||||
break;
|
||||
case MENU_ENUM_LABEL_FILE_BROWSER_OPEN_PICKER:
|
||||
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_filebrowser_open_picker);
|
||||
break;
|
||||
case MENU_ENUM_LABEL_ADD_TO_FAVORITES:
|
||||
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_add_to_favorites);
|
||||
break;
|
||||
|
@ -260,6 +260,7 @@ enum msg_hash_enums
|
||||
MSG_COMPILED_AGAINST_API,
|
||||
MSG_ERROR_LIBRETRO_CORE_REQUIRES_SPECIAL_CONTENT,
|
||||
MSG_ERROR_LIBRETRO_CORE_REQUIRES_CONTENT,
|
||||
MSG_ERROR_LIBRETRO_CORE_REQUIRES_VFS,
|
||||
MSG_SEVERAL_PATCHES_ARE_EXPLICITLY_DEFINED,
|
||||
MSG_DID_NOT_FIND_A_VALID_CONTENT_PATCH,
|
||||
MSG_FAILED_TO_ALLOCATE_MEMORY_FOR_PATCHED_CONTENT,
|
||||
@ -823,6 +824,9 @@ enum msg_hash_enums
|
||||
|
||||
MENU_LABEL(PARENT_DIRECTORY),
|
||||
|
||||
MENU_LABEL(FILE_BROWSER_OPEN_UWP_PERMISSIONS),
|
||||
MENU_LABEL(FILE_BROWSER_OPEN_PICKER),
|
||||
|
||||
MENU_ENUM_LABEL_CONTENT_ACTIONS,
|
||||
|
||||
/* Menu settings */
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" IgnorableNamespaces="uap mp">
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" IgnorableNamespaces="uap mp rescap">
|
||||
<Identity Name="1e4cf179-f3c2-404f-b9f3-cb2070a5aad8" Publisher="CN=krzys" Version="1.0.0.0" />
|
||||
<mp:PhoneIdentity PhoneProductId="1e4cf179-f3c2-404f-b9f3-cb2070a5aad8" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
|
||||
<Properties>
|
||||
@ -25,5 +25,6 @@
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<Capability Name="internetClientServer" />
|
||||
<rescap:Capability Name="broadFileSystemAccess" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
|
@ -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" />
|
||||
|
@ -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">
|
||||
|
@ -39,6 +39,10 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __WINRT__
|
||||
#include <uwp/uwp_func.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "../config.h"
|
||||
#endif
|
||||
@ -558,6 +562,8 @@ static bool content_file_load(
|
||||
retro_ctx_load_content_info_t load_info;
|
||||
size_t msg_size = 1024 * sizeof(char);
|
||||
char *msg = (char*)malloc(msg_size);
|
||||
rarch_system_info_t *system = runloop_get_system_info();
|
||||
bool used_vfs_fallback_copy = false;
|
||||
|
||||
msg[0] = '\0';
|
||||
|
||||
@ -606,7 +612,6 @@ static bool content_file_load(
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
#ifdef HAVE_COMPRESSION
|
||||
if ( !content_ctx->block_extract
|
||||
&& need_fullpath
|
||||
@ -618,6 +623,80 @@ static bool content_file_load(
|
||||
error_string))
|
||||
goto error;
|
||||
#endif
|
||||
|
||||
#ifdef __WINRT__
|
||||
/* TODO: When support for the 'actual' VFS is added, there will need to be some more logic here */
|
||||
if (!system->supports_vfs && !uwp_is_path_accessible_using_standard_io(path))
|
||||
{
|
||||
/* Fallback to a file copy into an accessible directory */
|
||||
|
||||
union string_list_elem_attr attributes;
|
||||
size_t new_basedir_size = PATH_MAX_LENGTH * sizeof(char);
|
||||
size_t new_path_size = PATH_MAX_LENGTH * sizeof(char);
|
||||
char *new_basedir = (char*)malloc(new_basedir_size);
|
||||
char *new_path = (char*)malloc(new_path_size);
|
||||
char* buf;
|
||||
int64_t len;
|
||||
|
||||
new_path[0] = '\0';
|
||||
new_basedir[0] = '\0';
|
||||
attributes.i = 0;
|
||||
|
||||
RARCH_LOG("Core does not support VFS - copying to cache directory\n");
|
||||
|
||||
if (!string_is_empty(content_ctx->directory_cache))
|
||||
strlcpy(new_basedir, content_ctx->directory_cache, new_basedir_size);
|
||||
if (string_is_empty(new_basedir) || !path_is_directory(new_basedir) || !uwp_is_path_accessible_using_standard_io(new_basedir))
|
||||
{
|
||||
RARCH_WARN("Tried copying to cache directory, but "
|
||||
"cache directory was not set or found. "
|
||||
"Setting cache directory to root of "
|
||||
"writable app directory...\n");
|
||||
strlcpy(new_basedir, uwp_dir_data, new_basedir_size);
|
||||
}
|
||||
|
||||
fill_pathname_join(new_path, new_basedir,
|
||||
path_basename(path), new_path_size);
|
||||
|
||||
/* TODO: This may fail on very large files... but copying large files is not a good idea anyway */
|
||||
if (!filestream_read_file(path, &buf, &len))
|
||||
{
|
||||
snprintf(msg,
|
||||
msg_size,
|
||||
"%s \"%s\". (during copy read)\n",
|
||||
msg_hash_to_str(MSG_COULD_NOT_READ_CONTENT_FILE),
|
||||
path);
|
||||
*error_string = strdup(msg);
|
||||
goto error;
|
||||
}
|
||||
if (!filestream_write_file(new_path, buf, len))
|
||||
{
|
||||
free(buf);
|
||||
snprintf(msg,
|
||||
msg_size,
|
||||
"%s \"%s\". (during copy write)\n",
|
||||
msg_hash_to_str(MSG_COULD_NOT_READ_CONTENT_FILE),
|
||||
path);
|
||||
*error_string = strdup(msg);
|
||||
goto error;
|
||||
}
|
||||
free(buf);
|
||||
|
||||
string_list_append(additional_path_allocs, new_path, attributes);
|
||||
info[i].path =
|
||||
additional_path_allocs->elems[additional_path_allocs->size - 1].data;
|
||||
|
||||
string_list_append(content_ctx->temporary_content,
|
||||
new_path, attributes);
|
||||
|
||||
free(new_basedir);
|
||||
free(new_path);
|
||||
|
||||
used_vfs_fallback_copy = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
RARCH_LOG("%s\n", msg_hash_to_str(MSG_CONTENT_LOADING_SKIPPED_IMPLEMENTATION_WILL_DO_IT));
|
||||
content_rom_crc = file_crc32(0, path);
|
||||
RARCH_LOG("CRC32: 0x%x .\n", (unsigned)content_rom_crc);
|
||||
@ -631,9 +710,19 @@ static bool content_file_load(
|
||||
|
||||
if (!core_load_game(&load_info))
|
||||
{
|
||||
snprintf(msg,
|
||||
if (used_vfs_fallback_copy)
|
||||
{
|
||||
/* This is probably going to fail on multifile ROMs etc. so give a visible explanation of what is likely wrong */
|
||||
snprintf(msg,
|
||||
msg_size,
|
||||
"%s.", msg_hash_to_str(MSG_ERROR_LIBRETRO_CORE_REQUIRES_VFS));
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(msg,
|
||||
msg_size,
|
||||
"%s.", msg_hash_to_str(MSG_FAILED_TO_LOAD_CONTENT));
|
||||
}
|
||||
*error_string = strdup(msg);
|
||||
goto error;
|
||||
}
|
||||
|
@ -26,6 +26,11 @@ extern char uwp_dir_install[PATH_MAX_LENGTH];
|
||||
extern char uwp_dir_data[PATH_MAX_LENGTH];
|
||||
extern char uwp_device_family[128];
|
||||
|
||||
void uwp_open_broadfilesystemaccess_settings(void);
|
||||
bool uwp_is_path_accessible_using_standard_io(char *path);
|
||||
bool uwp_drive_exists(const char *path);
|
||||
char* uwp_trigger_picker(void);
|
||||
|
||||
void* uwp_get_corewindow(void);
|
||||
|
||||
void uwp_input_next_frame(void);
|
||||
|
@ -627,4 +627,9 @@ extern "C" {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void uwp_open_broadfilesystemaccess_settings(void)
|
||||
{
|
||||
Windows::System::Launcher::LaunchUriAsync(ref new Uri("ms-settings:privacy-broadfilesystemaccess"));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user