AxibugEmuOnline/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSIO.cs

763 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#if UNITY_SWITCH
using nn.fs;
#endif
using System.Collections.Generic;
public class AxiNSIO
{
string save_name => AxiNS.instance.mount.SaveMountName;
public string save_path => $"{save_name}:/";
#if UNITY_SWITCH
private FileHandle fileHandle = new nn.fs.FileHandle();
#endif
static object commitLock = new object();
static bool bDirty = false;
bool CommitSave()
{
lock (commitLock)
{
#if UNITY_SWITCH && !UNITY_EDITOR
// 阻止用户在保存时,退出游戏 Switch 条例 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
nn.Result ret = FileSystem.Commit(save_name);
if (!ret.IsSuccess())
{
UnityEngine.Debug.LogError($"FileSystem.Commit({save_name}) 失败: " + ret.GetErrorInfo());
return false;
}
// 停止阻止用户退出游戏
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
bDirty = false;
return true;
#else
return false;
#endif
}
}
void SetCommitDirty()
{
lock (commitLock)
{
bDirty = true;
}
}
public void ApplyAutoCommit()
{
bool temp;
lock (commitLock)
{
temp = bDirty;
}
if (temp)
{
CommitSave();
}
}
/// <summary>
/// 检查Path是否存在
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public bool CheckPathExists(string filePath)
{
#if !UNITY_SWITCH
return false;
#else
nn.fs.EntryType entryType = 0;
nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, filePath);
//result.abortUnlessSuccess();
//这个异常捕获。真的别扭
return nn.fs.FileSystem.ResultPathAlreadyExists.Includes(result);
#endif
}
/// <summary>
/// 检查Path是否不存在
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public bool CheckPathNotFound(string filePath)
{
#if !UNITY_SWITCH
return false;
#else
nn.fs.EntryType entryType = 0;
nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, filePath);
//这个异常捕获。真的别扭
return nn.fs.FileSystem.ResultPathNotFound.Includes(result);
#endif
}
/// <summary>
/// 创建目录目录存在也会返回true
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public bool CreateDir(string filePath)
{
lock (commitLock)
{
#if !UNITY_SWITCH
return false;
#else
// 使用封装函数检查和创建父目录
if (!EnsureParentDirectory(filePath, true))
{
UnityEngine.Debug.LogError($"无法确保父目录,文件写入取消: {filePath}");
return false;
}
return true;
#endif
}
}
/// <summary>
/// 保存并创建文件(如果目录不存在回先自动创建目录)
/// </summary>
/// <param name="filePath"></param>
/// <param name="bw"></param>
/// <returns></returns>
public bool FileToSaveWithCreate(string filePath, System.IO.MemoryStream ms)
{
return FileToSaveWithCreate(filePath, ms.ToArray());
}
/// <summary>
/// 保存并创建文件(如果目录不存在回先自动创建目录)
/// </summary>
/// <param name="filePath"></param>
/// <param name="data"></param>
/// <returns></returns>
public AxiNSWait_FileToSaveByMSWithCreate FileToSaveWithCreateAsync(string filePath, System.IO.MemoryStream ms)
{
var wait = new AxiNSWait_FileToSaveByMSWithCreate(filePath, ms);
AxiNS.instance.wait.AddWait(wait);
return wait;
}
/// <summary>
/// 保存并创建文件(如果目录不存在回先自动创建目录)
/// </summary>
/// <param name="filePath"></param>
/// <param name="data"></param>
/// <param name="immediatelyCommit">是否立即Commit到物理存储</param>
/// <returns></returns>
public bool FileToSaveWithCreate(string filePath, byte[] data, bool immediatelyCommit = true)
{
lock (commitLock)
{
#if !UNITY_SWITCH
return false;
#else
if (!AxiNS.instance.mount.SaveIsMount)
{
UnityEngine.Debug.LogError($"Save 尚未挂载,无法存储 {filePath}");
return false;
}
nn.Result result;
#if UNITY_SWITCH && !UNITY_EDITOR
// 阻止用户在保存时,退出游戏
// Switch 条例 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
// 使用封装函数检查和创建父目录
if (!EnsureParentDirectory(filePath, true))
{
UnityEngine.Debug.LogError($"无法确保父目录,文件写入取消: {filePath}");
return false;
}
//string directoryPath = System.IO.Path.GetDirectoryName(filePath.Replace(save_path, ""));
//string fullDirectoryPath = $"{save_path}{directoryPath}";
//UnityEngine.Debug.Log($"检查父目录: {fullDirectoryPath}");
//nn.fs.EntryType entryType = 0;
//result = nn.fs.FileSystem.GetEntryType(ref entryType, fullDirectoryPath);
//if (!result.IsSuccess() && nn.fs.FileSystem.ResultPathNotFound.Includes(result))
//{
// UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 不存在,尝试创建 (判断依据 result=>{result.ToString()})");
// result = nn.fs.Directory.Create(fullDirectoryPath);
// if (!result.IsSuccess())
// {
// UnityEngine.Debug.LogError($"创建父目录失败: {result.GetErrorInfo()}");
// return false;
// }
// UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 创建成功");
//}
//else if (result.IsSuccess() && entryType != nn.fs.EntryType.Directory)
//{
// UnityEngine.Debug.LogError($"路径 {fullDirectoryPath} 已存在,但不是目录");
// return false;
//}
//else if (!result.IsSuccess())
//{
// UnityEngine.Debug.LogError($"检查父目录失败: {result.GetErrorInfo()}");
// return false;
//}
if (CheckPathNotFound(filePath))
{
UnityEngine.Debug.Log($"文件({filePath})不存在需要创建");
result = nn.fs.File.Create(filePath, data.Length); //this makes a file the size of your save journal. You may want to make a file smaller than this.
//result.abortUnlessSuccess();
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"创建文件失败 {filePath} : " + result.GetErrorInfo());
return false;
}
}
else
UnityEngine.Debug.Log($"文件({filePath})存在,不必创建");
result = File.Open(ref fileHandle, filePath, OpenFileMode.Write);
//result.abortUnlessSuccess();
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"失败 File.Open(ref filehandle, {filePath}, OpenFileMode.Write): " + result.GetErrorInfo());
return false;
}
UnityEngine.Debug.Log($"成功 File.Open(ref filehandle, {filePath}, OpenFileMode.Write)");
//nn.fs.WriteOption.Flush 应该就是覆盖写入
result = nn.fs.File.Write(fileHandle, 0, data, data.Length, nn.fs.WriteOption.Flush); // Writes and flushes the write at the same time
//result.abortUnlessSuccess();
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError("写入文件失败: " + result.GetErrorInfo());
return false;
}
UnityEngine.Debug.Log("写入文件成功: " + filePath);
nn.fs.File.Close(fileHandle);
#if UNITY_SWITCH && !UNITY_EDITOR
// 停止阻止用户退出游戏
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
if(immediatelyCommit)
{
//必须得提交,否则没有真实写入
return CommitSave();
}
else
{
SetCommitDirty();
return true;
}
#endif
}
}
/// <summary>
/// 保存并创建文件(如果目录不存在回先自动创建目录)
/// </summary>
/// <param name="filePath"></param>
/// <param name="data"></param>
/// <returns></returns>
public AxiNSWait_FileToSaveWithCreate FileToSaveWithCreateAsync(string filePath, byte[] data)
{
var wait = new AxiNSWait_FileToSaveWithCreate(filePath, data);
AxiNS.instance.wait.AddWait(wait);
return wait;
}
public byte[] LoadSwitchDataFile(string filename)
{
LoadSwitchDataFile(filename, out byte[] outputData);
return outputData;
}
public bool LoadSwitchDataFile(string filename, ref System.IO.MemoryStream ms)
{
if (LoadSwitchDataFile(filename, out byte[] outputData))
{
using (System.IO.BinaryWriter writer = new System.IO.BinaryWriter(ms))
{
writer.Write(outputData);
}
return true;
}
return false;
}
public bool LoadSwitchDataFile(string filename, out byte[] outputData)
{
#if !UNITY_SWITCH || UNITY_EDITOR
outputData = null;
return false;
#else
outputData = null;
if (!AxiNS.instance.mount.SaveIsMount)
{
UnityEngine.Debug.LogError($"Save 尚未挂载,无法读取 {filename}");
return false;
}
if (CheckPathNotFound(filename))
return false;
nn.Result result;
result = nn.fs.File.Open(ref fileHandle, filename, nn.fs.OpenFileMode.Read);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.Open 失败 {filename} : result=>{result.GetErrorInfo()}");
return false; // Could not open file. This can be used to detect if this is the first time a user has launched your game.
// (However, be sure you are not getting this error due to your file being locked by another process, etc.)
}
UnityEngine.Debug.Log($"nn.fs.File.Open 成功 {filename}");
long iFileSize = 0;
result = nn.fs.File.GetSize(ref iFileSize, fileHandle);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.GetSize 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
UnityEngine.Debug.Log($"nn.fs.File.GetSize 成功 {filename},size=>{iFileSize}");
byte[] loadedData = new byte[iFileSize];
result = nn.fs.File.Read(fileHandle, 0, loadedData, iFileSize);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.Read 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
UnityEngine.Debug.Log($"nn.fs.File.Read 成功 {filename}");
nn.fs.File.Close(fileHandle);
//for (int i = 0; i < loadedData.Length; i++)
//{
// UnityEngine.Debug.Log($"data[{i}]:{loadedData[i]}");
//}
outputData = loadedData;
return true;
#endif
}
public AxiNSWait_LoadSwitchDataFile LoadSwitchDataFileAsync(string filename)
{
var wait = new AxiNSWait_LoadSwitchDataFile(filename);
AxiNS.instance.wait.AddWait(wait);
return wait;
}
public bool GetDirectoryFiles(string path, out string[] entrys)
{
#if !UNITY_SWITCH || UNITY_EDITOR
entrys = null;
return false;
#else
return GetDirectoryEntrys(path,nn.fs.OpenDirectoryMode.File,out entrys);
#endif
}
public bool GetDirectoryDirs(string path, out string[] entrys)
{
#if !UNITY_SWITCH || UNITY_EDITOR
entrys = null;
return false;
#else
return GetDirectoryEntrys(path, nn.fs.OpenDirectoryMode.Directory, out entrys);
#endif
}
#if UNITY_SWITCH
public bool GetDirectoryEntrys(string path, nn.fs.OpenDirectoryMode type, out string[] entrys)
{
nn.fs.DirectoryHandle eHandle = new nn.fs.DirectoryHandle();
nn.Result result = nn.fs.Directory.Open(ref eHandle, path, type);
if (nn.fs.FileSystem.ResultPathNotFound.Includes(result))
{
UnityEngine.Debug.Log($"目录 {path} 不存在");
entrys = null;
return false;
}
long entryCount = 0;
nn.fs.Directory.GetEntryCount(ref entryCount, eHandle);
nn.fs.DirectoryEntry[] entries = new nn.fs.DirectoryEntry[entryCount];
long actualEntries = 0;
nn.fs.Directory.Read(ref actualEntries, entries, eHandle, entryCount);
entrys = new string[actualEntries];
for (int i = 0; i < actualEntries; i++)
{
entrys[i] = System.IO.Path.Combine(path, entries[i].name);
}
nn.fs.Directory.Close(eHandle);
return true;
}
#endif
#if UNITY_SWITCH
public bool GetDirectoryEntrysFullRecursion(string path, out string[] entrys)
{
nn.fs.DirectoryHandle eHandle = new nn.fs.DirectoryHandle();
nn.Result result = nn.fs.Directory.Open(ref eHandle, path, OpenDirectoryMode.All);
if (nn.fs.FileSystem.ResultPathNotFound.Includes(result))
{
UnityEngine.Debug.Log($"目录 {path} 不存在");
entrys = null;
return false;
}
long entryCount = 0;
nn.fs.Directory.GetEntryCount(ref entryCount, eHandle);
nn.fs.DirectoryEntry[] entries = new nn.fs.DirectoryEntry[entryCount];
long actualEntries = 0;
nn.fs.Directory.Read(ref actualEntries, entries, eHandle, entryCount);
List<string> temp = new List<string>();
for (int i = 0; i < actualEntries; i++)
{
string singlePath = System.IO.Path.Combine(path, entries[i].name);
temp.Add(singlePath);
if (entries[i].entryType == EntryType.Directory && GetDirectoryEntrysFullRecursion(singlePath, out string[] singleEntryList))
{
temp.AddRange(singleEntryList);
}
}
nn.fs.Directory.Close(eHandle);
entrys = temp.ToArray();
return true;
}
#endif
public IEnumerable<string> EnumerateFiles(string path, string searchPattern)
{
#if !UNITY_SWITCH || UNITY_EDITOR
yield break;
#else
// 将通配符转换为正则表达式(支持*和?
var regexPattern = "^" +
Regex.Escape(searchPattern)
.Replace("\\*", ".*")
.Replace("\\?", ".")
+ "$";
var regex = new Regex(regexPattern, RegexOptions.IgnoreCase);
if (!GetDirectoryEntrys(path, nn.fs.OpenDirectoryMode.File, out string[] entrys))
{
yield break;
}
for (int i = 0; i < entrys.Length; i++)
{
if (regex.IsMatch(System.IO.Path.GetFileName(entrys[i])))
{
yield return entrys[i];
}
}
#endif
}
public bool DeletePathFile(string filename)
{
#if !UNITY_SWITCH
return false;
#else
#if UNITY_SWITCH && !UNITY_EDITOR
// This next line prevents the user from quitting the game while saving.
// This is required for Nintendo Switch Guideline 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
if (CheckPathNotFound(filename))
return false;
nn.Result result;
result = nn.fs.File.Delete(filename);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.Delete 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
#if UNITY_SWITCH && !UNITY_EDITOR
// End preventing the user from quitting the game while saving.
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
return CommitSave();
#endif
}
public AxiNSWait_DeletePathFile DeletePathFileAsync(string filename)
{
var wait = new AxiNSWait_DeletePathFile(filename);
AxiNS.instance.wait.AddWait(wait);
return wait;
}
public bool DeletePathDir(string filename)
{
#if !UNITY_SWITCH
return false;
#else
#if UNITY_SWITCH && !UNITY_EDITOR
// This next line prevents the user from quitting the game while saving.
// This is required for Nintendo Switch Guideline 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
if (CheckPathNotFound(filename))
return false;
nn.Result result;
result = nn.fs.Directory.Delete(filename);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.Delete 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
#if UNITY_SWITCH && !UNITY_EDITOR
// End preventing the user from quitting the game while saving.
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
return CommitSave();
#endif
}
public AxiNSWait_DeletePathDir DeletePathDirAsync(string filename)
{
var wait = new AxiNSWait_DeletePathDir(filename);
AxiNS.instance.wait.AddWait(wait);
return wait;
}
/// <summary>
/// 递归删除目录
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
public bool DeleteRecursivelyPathDir(string filename)
{
#if !UNITY_SWITCH
return false;
#else
#if UNITY_SWITCH && !UNITY_EDITOR
// This next line prevents the user from quitting the game while saving.
// This is required for Nintendo Switch Guideline 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
if (CheckPathNotFound(filename))
return false;
nn.Result result;
result = nn.fs.Directory.DeleteRecursively(filename);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.DeleteRecursively 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
#if UNITY_SWITCH && !UNITY_EDITOR
// End preventing the user from quitting the game while saving.
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
return CommitSave();
#endif
}
/// <summary>
/// 递归删除情况
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
public bool CleanRecursivelyPathDir(string filename)
{
#if !UNITY_SWITCH
return false;
#else
#if UNITY_SWITCH && !UNITY_EDITOR
// This next line prevents the user from quitting the game while saving.
// This is required for Nintendo Switch Guideline 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
if (CheckPathNotFound(filename))
return false;
nn.Result result;
result = nn.fs.Directory.CleanRecursively(filename);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.DeleteRecursively 失败 {filename} : result=>{result.GetErrorInfo()}");
return false;
}
#if UNITY_SWITCH && !UNITY_EDITOR
// End preventing the user from quitting the game while saving.
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
return CommitSave();
#endif
}
public bool RenameDir(string oldpath, string newpath)
{
#if !UNITY_SWITCH
return false;
#else
#if UNITY_SWITCH && !UNITY_EDITOR
// This next line prevents the user from quitting the game while saving.
// This is required for Nintendo Switch Guideline 0080
UnityEngine.Switch.Notification.EnterExitRequestHandlingSection();
#endif
if (CheckPathNotFound(oldpath))
return false;
nn.Result result;
result = nn.fs.Directory.Rename(oldpath, newpath);
if (result.IsSuccess() == false)
{
UnityEngine.Debug.LogError($"nn.fs.File.Rename 失败 {oldpath} to {newpath} : result=>{result.GetErrorInfo()}");
return false;
}
#if UNITY_SWITCH && !UNITY_EDITOR
// End preventing the user from quitting the game while saving.
UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection();
#endif
return CommitSave();
#endif
}
bool EnsureParentDirectory(string filePath, bool bAutoCreateDir = true)
{
#if !UNITY_SWITCH
return false;
#else
// 参数校验
if (string.IsNullOrEmpty(filePath))
{
UnityEngine.Debug.LogError($"无效参数filePath={filePath}");
return false;
}
// 提取路径前缀(如 save:/、sd:/
int prefixEndIndex = filePath.IndexOf(":/");
if (prefixEndIndex == -1)
{
UnityEngine.Debug.LogError($"文件路径 {filePath} 格式无效,未找到 ':/' 前缀");
return false;
}
string pathPrefix = filePath.Substring(0, prefixEndIndex + 2); // 提取前缀,例如 "save:/"
string relativePath = filePath.Substring(prefixEndIndex + 2); // 移除前缀,得到相对路径
// 检查挂载状态
if (!IsMountPointAccessible(pathPrefix))
{
UnityEngine.Debug.LogError($"挂载点 {pathPrefix} 未挂载,无法操作路径 {filePath}");
return false;
}
// 提取父目录路径
string directoryPath = System.IO.Path.GetDirectoryName(relativePath); // 获取父目录相对路径
if (string.IsNullOrEmpty(directoryPath))
{
UnityEngine.Debug.Log($"文件路径 {filePath} 无需创建父目录(位于根目录)");
return true; // 根目录无需创建
}
string fullDirectoryPath = $"{pathPrefix}{directoryPath}"; // 拼接完整父目录路径
UnityEngine.Debug.Log($"检查父目录: {fullDirectoryPath}");
// 检查路径是否存在及其类型
nn.fs.EntryType entryType = 0;
nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, fullDirectoryPath);
if (!result.IsSuccess() && nn.fs.FileSystem.ResultPathNotFound.Includes(result))
{
if (bAutoCreateDir)
{
// 路径不存在,尝试创建
UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 不存在,尝试创建 (判断依据 result=>{result.ToString()})");
result = nn.fs.Directory.Create(fullDirectoryPath);
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"创建父目录失败: {result.GetErrorInfo()}");
return false;
}
UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 创建成功");
return true;
}
return false;
}
else if (result.IsSuccess() && entryType != nn.fs.EntryType.Directory)
{
// 路径存在,但不是目录
UnityEngine.Debug.LogError($"路径 {fullDirectoryPath} 已存在,但不是目录");
return false;
}
else if (!result.IsSuccess())
{
// 其他错误
UnityEngine.Debug.LogError($"检查父目录失败: {result.GetErrorInfo()}");
return false;
}
// 路径存在且是目录
UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 已存在且有效");
return true;
#endif
}
/// <summary>
/// 检查指定挂载点是否可访问
/// </summary>
/// <param name="pathPrefix">路径前缀,例如 "save:/" 或 "sd:/"</param>
/// <returns>挂载点是否可访问</returns>
bool IsMountPointAccessible(string pathPrefix)
{
#if !UNITY_SWITCH
return false;
#else
if (string.IsNullOrEmpty(pathPrefix))
{
UnityEngine.Debug.LogError($"无效挂载点: {pathPrefix}");
return false;
}
// 根据前缀判断挂载点类型并检查挂载状态
if (pathPrefix == $"{save_name}:/")
{
if (!AxiNS.instance.mount.SaveIsMount)
{
UnityEngine.Debug.LogError($"{save_name}:/ 未挂载");
return false;
}
return true;
}
else if (pathPrefix == "sd:/")
{
long freeSpace = 0;
// 检查 SD 卡挂载状态(示例,需根据实际实现调整)
nn.Result result = nn.fs.FileSystem.GetFreeSpaceSize(ref freeSpace, "sd:/");
if (!result.IsSuccess())
{
UnityEngine.Debug.LogError($"sd:/ 未挂载或无法访问: {result.GetErrorInfo()}");
return false;
}
return true;
}
else
{
UnityEngine.Debug.LogWarning($"未知挂载点 {pathPrefix},假定已挂载");
return true; // 其他挂载点需根据实际需求实现
}
#endif
}
}