我正在开发 Meta Quest 2 的应用程序。我基本上有两个应用程序。一个称为“ADMIN”,另一个称为“CLIENT”。您可以在“管理员”应用程序中配置和保存一些数据,然后应在“客户端”应用程序中加载它们。 问题是,当我尝试读取“CLIENT”应用程序中的文件时,它会抛出一个错误:“UnauthorizedAccessException 访问路径存储/模拟/0/文档被拒绝”,即使我已正确设置所有权限。
我的保存代码是:
using System;
using System.IO;
using System.Text;
using UnityEngine;
using UnityEngine.Android;
using System.Security.AccessControl;
using Microsoft.Win32.SafeHandles;
/// <summary>
/// Save System.
/// Use these APIs to Save/Load game data.
/// </summary>
public static class SaveSystem
{
private static string selectedFilePath;
private static ISaveSystemSerializer m_Serializer = new SaveSystemJsonSerializer();
private static Encoding m_Encoding = Encoding.UTF8;
/// <summary>
/// Gets or sets the serializer.
/// </summary>
/// <value>The serializer.</value>
public static ISaveSystemSerializer Serializer
{
get => m_Serializer ??= new SaveSystemJsonSerializer();
set => m_Serializer = value;
}
/// <summary>
/// Gets or sets the encoding.
/// </summary>
/// <value>The encoding.</value>
public static Encoding DefaultEncoding
{
get => m_Encoding ??= Encoding.UTF8;
set => m_Encoding = value;
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="FG_Saving.SaveSystem"/> encrypt data by default.
/// </summary>
/// <value><c>true</c> if encode; otherwise, <c>false</c>.</value>
public static bool Encode { get; set; } = false;
public static string Path { get; set; } = "storage/emulated/0/Documents";
/// <summary>
/// Gets or sets the encryption password.
/// </summary>
/// <value>The encryption password.</value>
public static string EncodePassword { get; set; } = "dabkaksdksadkj";
/// <summary>
/// Gets or sets a value indicating whether this <see cref="FG_Saving.SaveSystem"/> log error.
/// </summary>
/// <value><c>true</c> if log error; otherwise, <c>false</c>.</value>
public static bool LogError { get; set; } = false;
/// <summary>
/// Saves data using the identifier.
/// </summary>
/// <param name="identifier">Identifier.</param>
/// <param name="obj">Object to save.</param>
/// <param name="saveFile"> Save File Number</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
public static void Save<T>(string identifier, T obj, int saveFile)
{
Save<T>(identifier, obj, saveFile, Encode, EncodePassword, Serializer, DefaultEncoding);
}
/// <summary>
/// Saves data using the identifier.
/// </summary>
/// <param name="identifier">Identifier.</param>
/// <param name="data">data to save.</param>
/// <param name="saveFile"> Save File Number</param>
/// <param name="encode">Encrypt the data?</param>
/// <param name="password">Encryption Password.</param>
/// <param name="serializer">Serializer.</param>
/// <param name="encoding">Encoding.</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
public static void Save<T>(string identifier, T data, int saveFile, bool encode, string password, ISaveSystemSerializer serializer, Encoding encoding)
{
if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite) ||
Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead))
{
Permission.RequestUserPermissions(new []{Permission.ExternalStorageRead, Permission.ExternalStorageWrite } );
Debug.Log("Permission Granted");
}
if (string.IsNullOrEmpty(identifier)) throw new System.ArgumentNullException(nameof(identifier));
serializer ??= Serializer;
encoding ??= DefaultEncoding;
string filePath = "";
if (identifier == "Settings")
{
filePath = $"{Path}/{identifier + ".cfg"}";
}
else
{
filePath = $"{Path}/{identifier}{saveFile:D3}.vrm";
}
data ??= default(T);
Stream stream = null;
if (encode)
{
stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
}
else
{
stream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
}
serializer.Serialize(data, stream, encoding);
stream.Dispose();
Debug.Log("saved" + filePath);
}
/// <summary>
/// Loads data using identifier.
/// </summary>
/// <param name="identifier">Identifier.</param>
/// <param name="saveSlot"> The save slot to load from</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
public static T Load<T>(string identifier, int saveSlot)
{
return Load<T>(identifier, saveSlot, default(T), Encode, EncodePassword, Serializer, DefaultEncoding);
}
/// <summary>
/// Loads data using identifier.
/// </summary>
/// <param name="identifier">Identifier.</param>
/// <param name="saveSlot"> the save slot to load from</param>
/// <param name="defaultValue">Default Value.</param>
/// <param name="encode">Load encrypted data? (set it to true if you have used encryption in save)</param>
/// <param name="password">Encryption Password.</param>
/// <param name="serializer">Serializer.</param>
/// <param name="encoder">Encoder.</param>
/// <param name="encoding">Encoding.</param>
/// <param name="path">Path.</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
public static T Load<T>(string identifier, int saveSlot, T defaultValue, bool encode, string password, ISaveSystemSerializer serializer, Encoding encoding)
{
if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead))
{
Permission.RequestUserPermissions(new []{Permission.ExternalStorageRead, Permission.ExternalStorageWrite } );
Debug.Log("Permission Granted");
}
if (string.IsNullOrEmpty(identifier))
throw new ArgumentNullException(nameof(identifier));
serializer ??= Serializer;
encoding ??= DefaultEncoding;
defaultValue ??= default(T);
T result = defaultValue;
string filePath = "";
if (identifier == "Settings")
filePath = $"{Path}/{identifier + ".cfg"}";
else
filePath = $"{Path}/{identifier}{saveSlot:D3}.vrm";
if (Exists(identifier, saveSlot))
{
Stream stream = null;
stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
result = serializer.Deserialize<T>(stream, encoding);
stream.Dispose();
Debug.Log("loaded" + filePath);
return result;
}
return default;
}
/// <summary>
/// Checks whether the specified identifier exists or not.
/// </summary>
/// <param name="identifier">Identifier.</param>
/// <param name="saveSlot"> The save slot to load from</param>
public static bool Exists(string identifier, int saveSlot) =>
File.Exists($"{Path}/{identifier}{saveSlot:D3}.vrm");
/// <summary>
/// Delete the specified identifier and path.
/// </summary>
/// <param name="identifier">Identifier.</param>
/// <param name="saveSlot"> the save slot to load from </param>
/// <param name="path">Path.</param>
public static void Delete(string identifier, int saveSlot)
{
if (string.IsNullOrEmpty(identifier))
throw new System.ArgumentNullException(nameof(identifier));
string filePath = "";
if (identifier == "Settings")
filePath = $"{Path}/{identifier + ".cfg"}";
else
filePath = $"{Path}/{identifier}{saveSlot:D3}.vrm";
if (!Exists(filePath, saveSlot)) return;
File.Delete(filePath);
}
}
我尝试使用 unity 的 AndroidJavaClass 加载文件,但我不明白 java :) 尽管我想出了一些办法,但根本不起作用。
我不使用 Unity,但我认为,对于多个游戏引擎,对于 Meta Quest 2,这个问题是相同的。
我做了什么来使文件访问与 System.IO C# 类一起工作,因为它已经设置为可以与远程调试一起使用:
/storage/emulated/0/Android/data/<javaPackageName>/files/…
作为文件路径。显然,其他文件无法修改。internal storage…/Android/data/<javaPackageName>/files
中。实际上,游戏编辑器应该在远程调试游戏时就已经这样做了,但这是一个不丢失文件的安全测试方法。