#if UNITY_SWITCH using nn.fs; using System.Text.RegularExpressions; #endif using System.Collections.Generic; using System; 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(); } } /// /// 检查Path是否存在 /// /// /// 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(); //这个异常捕获。真的别扭 //日,FileSystem.ResultPathAlreadyExists 貌似不太行 //return nn.fs.FileSystem.ResultPathAlreadyExists.Includes(result); return !nn.fs.FileSystem.ResultPathNotFound.Includes(result); #endif } /// /// 检查Path是否不存在 /// /// /// 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 } /// /// 创建目录,目录存在也会返回true /// /// /// public bool CreateDir(string dirpath) { UnityEngine.Debug.Log($"CreateDir: {dirpath}"); lock (commitLock) { #if !UNITY_SWITCH return false; #else // 使用封装函数检查和创建父目录 if (!EnsureParentDirectory(dirpath, true)) { UnityEngine.Debug.LogError($"无法确保父目录,文件写入取消: {dirpath}"); return false; } return true; #endif } } /// /// 保存并创建文件(如果目录不存在回先自动创建目录) /// /// /// /// public bool FileToSaveWithCreate(string filePath, System.IO.MemoryStream ms) { return FileToSaveWithCreate(filePath, ms.ToArray()); } /// /// 保存并创建文件(如果目录不存在回先自动创建目录) /// /// /// /// public AxiNSWait_FileToSaveByMSWithCreate FileToSaveWithCreateAsync(string filePath, System.IO.MemoryStream ms) { var wait = new AxiNSWait_FileToSaveByMSWithCreate(filePath, ms); AxiNS.instance.wait.AddWait(wait); return wait; } /// /// 保存并创建文件(如果目录不存在回先自动创建目录) /// /// /// /// 是否立即Commit到物理存储 /// public bool FileToSaveWithCreate(string filePath, byte[] data, bool immediatelyCommit = true) { UnityEngine.Debug.Log($"FileToSaveWithCreate: {filePath}"); lock (commitLock) { #if !UNITY_SWITCH return false; #else if (!AxiNS.instance.mount.SaveIsMount) { UnityEngine.Debug.LogError($"Save 尚未挂载,无法存储 {filePath}"); return false; } nn.Result result; //取出父级目录 string dirpath = string.Empty; //string filePath = "save:/AxibugEmu/Caches/Texture/516322966"; string mountRoot = null; int colonSlashIndex = filePath.IndexOf(":/"); if (colonSlashIndex > 0) mountRoot = filePath.Substring(0, colonSlashIndex + 1); // 例如 "save:" int lastSlashIndex = filePath.LastIndexOf('/'); if (lastSlashIndex >= 0) { string parent = filePath.Substring(0, lastSlashIndex); if (mountRoot != null && !parent.Equals(mountRoot, StringComparison.OrdinalIgnoreCase)) dirpath = parent; } if (!string.IsNullOrWhiteSpace(dirpath)) { // 使用封装函数检查和创建父目录 if (!EnsureParentDirectory(dirpath, 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 UNITY_SWITCH && !UNITY_EDITOR // 阻止用户在保存时,退出游戏 // Switch 条例 0080 UnityEngine.Switch.Notification.EnterExitRequestHandlingSection(); #endif 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; } //读取文件Handle result = File.Open(ref fileHandle, filePath, OpenFileMode.Write); } else { //读取文件Handle result = File.Open(ref fileHandle, filePath, OpenFileMode.Write); long currsize = 0; File.GetSize(ref currsize, fileHandle); if (currsize == data.Length) { UnityEngine.Debug.Log($"文件({filePath})存在,长度一致,不用重新创建"); } else { UnityEngine.Debug.Log($"文件({filePath})存在,长度不一致,先删除再重建"); nn.fs.File.Close(fileHandle); //删除 File.Delete(filePath); //重新创建 result = nn.fs.File.Create(filePath, data.Length); if (!result.IsSuccess()) { UnityEngine.Debug.LogError($"创建文件失败 {filePath} : " + result.GetErrorInfo()); return false; } //重新读取文件Handle result = File.Open(ref fileHandle, filePath, OpenFileMode.Write); } } // //OpenFileMode.AllowAppend 好像不可用 // //result = File.Open(ref fileHandle, filePath, OpenFileMode.AllowAppend); // 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 } } /// /// 保存并创建文件(如果目录不存在回先自动创建目录) /// /// /// /// 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) { byte[] outputData; LoadSwitchDataFile(filename, out outputData); return outputData; } public bool LoadSwitchDataFile(string filename, ref System.IO.MemoryStream ms) { byte[] outputData; if (LoadSwitchDataFile(filename, out 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 public bool GetDirectoryEntrysFullRecursion(string path, out string[] entrys) { #if UNITY_SWITCH 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 temp = new List(); 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; #else entrys = null; return false; #endif } public IEnumerable 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; } public bool DeletePathDirRecursively(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.Recursively 失败 {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_DeletePathDirRecursively DeletePathDirRecursivelyAsync(string filename) { var wait = new AxiNSWait_DeletePathDirRecursively(filename); AxiNS.instance.wait.AddWait(wait); return wait; } /// /// 递归删除目录 /// /// /// 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 } /// /// 递归删除情况 /// /// /// 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 } #if UNITY_SWITCH bool CreateLoopDir(string path) { // 检查路径是否存在及其类型 nn.fs.EntryType entryType = 0; nn.Result result; List needcreatedirs = new List(); string node = path; while (true) { if (string.IsNullOrEmpty(node) || node.EndsWith(":/")) break; if (!CheckPathNotFound(node)) needcreatedirs.Insert(0, node); else break; result = nn.fs.FileSystem.GetEntryType(ref entryType, node); if (!result.IsSuccess()) needcreatedirs.Insert(0, node); else break;//文件已存在 node = System.IO.Path.GetDirectoryName(node); } for (int i = 0; i < needcreatedirs.Count; i++) { UnityEngine.Debug.LogError($"需要创建的目录: {needcreatedirs[i]}"); } for (int i = 0; i < needcreatedirs.Count; i++) { //result = nn.fs.Directory.Create(needcreatedirs[i]); //if (!result.IsSuccess()) //{ // UnityEngine.Debug.LogError($"创建父目录失败: {result.GetErrorInfo()}"); // return false; //} //UnityEngine.Debug.Log($"父目录 {needcreatedirs[i]} 创建成功"); //CommitSave(); } return false; return true; } #endif /// /// 解析路径并获取其所有父级目录(从直接父目录到根目录),并排除存储设备挂载根节点(如"save:"或"sd:")。 /// 专为Switch平台设计,正确处理如"save:/"或"sd:/"开头的路径。 /// /// 输入的绝对路径 /// 输出参数,解析出的最直接目录(文件所在目录或目录自身) /// 输出参数,从直接父目录到挂载点下一级的列表(不包含挂载根节点) /// 操作是否成功(路径格式基本有效) public bool TryGetDirectoryAndParentsExcludingRoot(string inputPath, out string resolvedDirectory, out List parentDirectories) { // 捕获路径中包含非法字符引发的异常 //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->{inputPath}"); resolvedDirectory = null; parentDirectories = new List(); if (string.IsNullOrWhiteSpace(inputPath)) { UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->string.IsNullOrWhiteSpace({inputPath})==false"); return false; } string normalizedPath = inputPath.Replace('\\', '/').Trim(); // 统一使用正斜杠 //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->normalizedPath=>{normalizedPath}"); try { // 1. 判断路径类型并解析出最直接的目标目录 bool isLikelyFile = false; if (normalizedPath.EndsWith("/")) { //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->normalizedPath.EndsWith(\"/\") == true"); resolvedDirectory = normalizedPath.TrimEnd('/'); } else { int lastSeparatorIndex = normalizedPath.LastIndexOf('/'); //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->lastSeparatorIndex->{lastSeparatorIndex}"); string lastPart = (lastSeparatorIndex >= 0) ? normalizedPath.Substring(lastSeparatorIndex + 1) : normalizedPath; if (string.IsNullOrEmpty(lastPart) || lastPart.Equals("..") || !lastPart.Contains(".")) { //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->step1"); resolvedDirectory = normalizedPath; } else { //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->step2"); isLikelyFile = true; //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->lastSeparatorIndex=>{lastSeparatorIndex}"); if (lastSeparatorIndex >= 0) { resolvedDirectory = normalizedPath.Substring(0, lastSeparatorIndex); } else { resolvedDirectory = normalizedPath; // 可能是根目录下的文件,但这种情况在Switch特殊路径中较少 } } } //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->resolvedDirectory=>{resolvedDirectory}"); if (string.IsNullOrEmpty(resolvedDirectory)) { //UnityEngine.Debug.LogError($"TryGetDirectoryAndParentsExcludingRoot->string.IsNullOrEmpty(resolvedDirectory) == false"); return false; } //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->step3"); // 2. 提取并检查挂载根节点(如 "save:" 或 "sd:") string mountRoot = null; int colonSlashIndex = resolvedDirectory.IndexOf(":/"); if (colonSlashIndex > 0) { mountRoot = resolvedDirectory.Substring(0, colonSlashIndex + 1); // 例如 "save:" } //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->mountRoot=>{mountRoot}"); // 检查挂载状态 if (!IsMountPointAccessible(mountRoot + "/")) { UnityEngine.Debug.LogError($"挂载点 {mountRoot + "/"} 未挂载,无法操作路径 {inputPath}"); return false; } //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->step4"); // 3. 手动分割路径,收集父目录,并在到达挂载根节点时停止 string currentPath = resolvedDirectory; //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->currentPath=>{currentPath}"); while (!string.IsNullOrEmpty(currentPath)) { //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->step5"); // 检查当前路径是否已经是挂载根节点(例如 "save:") if (mountRoot != null && currentPath.Equals(mountRoot, StringComparison.OrdinalIgnoreCase)) { break; // 停止添加,不将挂载根节点加入父目录列表 } parentDirectories.Add(currentPath); // 将当前层级的路径加入列表 int lastSlashIndex = currentPath.LastIndexOf('/'); if (lastSlashIndex < 0) { break; // 没有分隔符了,跳出循环 } string nextParent = currentPath.Substring(0, lastSlashIndex); // 检查下一级父目录是否就是挂载根节点(例如 "save:"),如果是,则停止 if (mountRoot != null && nextParent.Equals(mountRoot, StringComparison.OrdinalIgnoreCase)) { // 可以选择是否将 mountRoot 加入列表,这里根据需求不加入 break; } currentPath = nextParent; } //UnityEngine.Debug.Log($"TryGetDirectoryAndParentsExcludingRoot->step6 True Return"); return true; } catch (ArgumentException ex) { // 捕获路径中包含非法字符引发的异常 UnityEngine.Debug.LogError($"路径中包含非法字符: {ex.Message}"); return false; } } 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); // 获取父目录相对路径 //UnityEngine.Debug.Log($"提取 {relativePath} 的 父级路径:{directoryPath}"); //if (string.IsNullOrEmpty(directoryPath)) //{ // UnityEngine.Debug.Log($"文件路径 {filePath} 无需创建父目录(位于根目录)"); // return true; // 根目录无需创建 //} //string fullDirectoryPath = $"{pathPrefix}{directoryPath}"; // 拼接完整父目录路径 if (!TryGetDirectoryAndParentsExcludingRoot(filePath, out string fullDirectoryPath, out List parentDirectories)) { UnityEngine.Debug.LogError($"TryGetDirectoryAndParentsExcludingRoot 操作失败:{filePath}"); return false; } 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()})"); for (int i = parentDirectories.Count - 1; i >= 0; i--) { UnityEngine.Debug.Log($">>待检查目录: {parentDirectories[i]}"); } for (int i = parentDirectories.Count - 1; i >= 0; i--) { string dir = parentDirectories[i]; if (CheckPathNotFound(dir)) { UnityEngine.Debug.Log($"需要创建的目录: {dir}"); result = nn.fs.Directory.Create(dir); if (!result.IsSuccess()) { UnityEngine.Debug.LogError($"创建父 {dir} 目录失败: {result.GetErrorInfo()}"); return false; } UnityEngine.Debug.Log($"父目录 {dir} 创建成功"); //CommitSave(); } else { UnityEngine.Debug.Log($"目录已存在,无需创建: {dir}"); } } //result = nn.fs.Directory.Create(fullDirectoryPath); //if (!result.IsSuccess()) //{ // UnityEngine.Debug.LogError($"创建父目录失败: {result.GetErrorInfo()}"); // return false; //} //if (!CreateLoopDir(fullDirectoryPath)) // 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 } /// /// 检查指定挂载点是否可访问 /// /// 路径前缀,例如 "save:/" 或 "sd:/" /// 挂载点是否可访问 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 } }