From ebdc044978e13d005716358bf600905c169cac99 Mon Sep 17 00:00:00 2001 From: sin365 <353374337@qq.com> Date: Tue, 8 Apr 2025 01:39:06 +0800 Subject: [PATCH] 1.0 --- Assets/Scripts.meta | 8 + Assets/Scripts/AxiNSApi.meta | 8 + Assets/Scripts/AxiNSApi/AxiNS.cs | 39 ++ Assets/Scripts/AxiNSApi/AxiNS.cs.meta | 11 + Assets/Scripts/AxiNSApi/AxiNSErrCode.cs | 367 ++++++++++++ Assets/Scripts/AxiNSApi/AxiNSErrCode.cs.meta | 11 + Assets/Scripts/AxiNSApi/AxiNSIO.cs | 568 +++++++++++++++++++ Assets/Scripts/AxiNSApi/AxiNSIO.cs.meta | 11 + Assets/Scripts/AxiNSApi/AxiNSMount.cs | 134 +++++ Assets/Scripts/AxiNSApi/AxiNSMount.cs.meta | 11 + Assets/Scripts/AxiNSApi/AxiNSSDCard.cs | 23 + Assets/Scripts/AxiNSApi/AxiNSSDCard.cs.meta | 11 + Assets/Scripts/AxiNSApi/AxiNSUser.cs | 106 ++++ Assets/Scripts/AxiNSApi/AxiNSUser.cs.meta | 11 + 14 files changed, 1319 insertions(+) create mode 100644 Assets/Scripts.meta create mode 100644 Assets/Scripts/AxiNSApi.meta create mode 100644 Assets/Scripts/AxiNSApi/AxiNS.cs create mode 100644 Assets/Scripts/AxiNSApi/AxiNS.cs.meta create mode 100644 Assets/Scripts/AxiNSApi/AxiNSErrCode.cs create mode 100644 Assets/Scripts/AxiNSApi/AxiNSErrCode.cs.meta create mode 100644 Assets/Scripts/AxiNSApi/AxiNSIO.cs create mode 100644 Assets/Scripts/AxiNSApi/AxiNSIO.cs.meta create mode 100644 Assets/Scripts/AxiNSApi/AxiNSMount.cs create mode 100644 Assets/Scripts/AxiNSApi/AxiNSMount.cs.meta create mode 100644 Assets/Scripts/AxiNSApi/AxiNSSDCard.cs create mode 100644 Assets/Scripts/AxiNSApi/AxiNSSDCard.cs.meta create mode 100644 Assets/Scripts/AxiNSApi/AxiNSUser.cs create mode 100644 Assets/Scripts/AxiNSApi/AxiNSUser.cs.meta diff --git a/Assets/Scripts.meta b/Assets/Scripts.meta new file mode 100644 index 0000000..23e9973 --- /dev/null +++ b/Assets/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0697ef00029fba24dbd9ef8cda41ebc6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AxiNSApi.meta b/Assets/Scripts/AxiNSApi.meta new file mode 100644 index 0000000..b6bc204 --- /dev/null +++ b/Assets/Scripts/AxiNSApi.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 253b86161e6eafd4a8e1ea2275733fec +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AxiNSApi/AxiNS.cs b/Assets/Scripts/AxiNSApi/AxiNS.cs new file mode 100644 index 0000000..648bbd6 --- /dev/null +++ b/Assets/Scripts/AxiNSApi/AxiNS.cs @@ -0,0 +1,39 @@ +#if UNITY_SWITCH +using nn.account; +#endif + +public class AxiNS +{ + static AxiNS _instance; + public static AxiNS instance + { + get + { + if (_instance == null) + _instance = new AxiNS(); + return _instance; + } + } + + public AxiNSUser user; + public AxiNSMount mount; + public AxiNSIO io; + AxiNS() + { + user = new AxiNSUser(); + mount = new AxiNSMount(); + io = new AxiNSIO(); + } + + /// + /// 初始化(最好在项目第一时间初始化,保证先初始化再使用某些东西,才不闪退) + /// + public void Init() + { +#if UNITY_SWITCH + if (!user.GetUserID(out Uid uid)) + return; + mount.MountSave(uid); +#endif + } +} diff --git a/Assets/Scripts/AxiNSApi/AxiNS.cs.meta b/Assets/Scripts/AxiNSApi/AxiNS.cs.meta new file mode 100644 index 0000000..4b94514 --- /dev/null +++ b/Assets/Scripts/AxiNSApi/AxiNS.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 52541c757d45c4c488726bcc39f73ba6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AxiNSApi/AxiNSErrCode.cs b/Assets/Scripts/AxiNSApi/AxiNSErrCode.cs new file mode 100644 index 0000000..b4c4af2 --- /dev/null +++ b/Assets/Scripts/AxiNSApi/AxiNSErrCode.cs @@ -0,0 +1,367 @@ +public static class AxiNSErrCode +{ +#if UNITY_SWITCH + public static string GetErrorInfo(this nn.Result result) + { + if (result.IsSuccess()) + return "NoErr"; + return GetErrorDetails(result.GetModule(), result.GetDescription()); + } +#endif + /// + /// 根据模块 ID 和描述 ID 返回任天堂 Switch 错误码的含义、可能原因及解决办法。 + /// + /// 模块 ID + /// 描述 ID + /// 包含错误码、含义、可能原因及解决办法的字符串 + public static string GetErrorDetails(int moduleId, int descriptionId) + { + string errorCode = $"2{moduleId:D3}-{descriptionId:D4}"; // 格式化为 2XXX-YYYY + string meaning = "未知错误"; + string causeAndSolution = "未知错误,请检查日志或联系任天堂支持。"; + + switch (moduleId) + { + case 2: // nn::fs (文件系统) + switch (descriptionId) + { + case 1: + meaning = "ResultPathNotFound"; + causeAndSolution = "路径未找到。检查路径是否正确,确保父目录存在。使用 nn.fs.Directory.Create 创建父目录。"; + break; + case 2: + meaning = "ResultPermissionDenied"; + causeAndSolution = "权限被拒绝。可能是 Atmosphere 限制了 save:/ 的写权限。尝试调整 Atmosphere 配置或使用 sd:/ 挂载点。"; + break; + case 3: + meaning = "ResultPathAlreadyExists"; + causeAndSolution = "路径已存在。检查路径是否被占用,删除或重命名现有文件/目录。"; + break; + case 5: + meaning = "ResultTargetLocked"; + causeAndSolution = "目标被锁定。可能是文件正在使用中,关闭相关程序后重试。"; + break; + case 7: + meaning = "ResultTargetNotFound"; + causeAndSolution = "目标未找到。确认目标文件/目录是否存在,检查路径拼写。"; + break; + } + break; + + case 5: // nn::err (错误处理) + switch (descriptionId) + { + case 3: + meaning = "microSD 卡相关错误"; + causeAndSolution = "无法下载软件,可能是 microSD 卡损坏。移除 microSD 卡,重新插入,或更换卡。"; + break; + } + break; + + case 16: // nn::oe (操作系统错误) + switch (descriptionId) + { + case 247: + meaning = "microSD 卡存储问题"; + causeAndSolution = "microSD 卡损坏或不兼容。更换 microSD 卡,或将默认存储位置设置为系统内存。"; + break; + case 390: + meaning = "不支持的 microSD 卡"; + causeAndSolution = "microSD 卡格式不受支持。格式化为 exFAT 或 FAT32 后重试。"; + break; + case 601: + meaning = "microSD 卡数据损坏"; + causeAndSolution = "microSD 卡数据损坏。备份数据后格式化卡,或更换新卡。"; + break; + } + break; + + case 21: // nn::settings (设置) + switch (descriptionId) + { + case 3: + meaning = "系统软件未更新或损坏"; + causeAndSolution = "系统固件版本过旧或损坏。更新系统固件,或重新安装系统。"; + break; + } + break; + + case 101: // nn::fssrv (文件系统服务) + switch (descriptionId) + { + case 1: + meaning = "系统错误"; + causeAndSolution = "通用系统错误。重启 Switch,若问题持续,联系任天堂支持。"; + break; + case 2: + meaning = "固件损坏"; + causeAndSolution = "系统固件损坏。尝试更新系统,或恢复出厂设置(注意备份数据)。"; + break; + } + break; + + case 107: // nn::nim (网络安装管理) + switch (descriptionId) + { + case 427: + meaning = "固件损坏"; + causeAndSolution = "系统固件损坏。更新系统固件,或重新安装系统。"; + break; + case 445: + meaning = "硬件损坏或盗版内容"; + causeAndSolution = "可能是硬件损坏或存在盗版内容。删除盗版内容,重启 Switch,若无效联系任天堂支持。"; + break; + } + break; + + case 110: // nn::socket (网络套接字) + switch (descriptionId) + { + case 1000: + meaning = "网络连接失败"; + causeAndSolution = "无法连接到网络。检查网络连接,重启路由器,尝试切换到其他网络频段(2.4GHz/5GHz)。"; + break; + case 2003: + meaning = "无线网络连接失败"; + causeAndSolution = "无线信号弱或不稳定。靠近路由器,移除干扰物,或重启路由器。"; + break; + case 2004: + meaning = "网络设置不支持"; + causeAndSolution = "网络安全类型不受支持。Switch 支持 WEP/WPA/WPA2,调整路由器设置后重试。"; + break; + case 2091: + meaning = "有线网络连接失败"; + causeAndSolution = "有线连接问题。检查网线是否插好,重启 Switch 和路由器。"; + break; + case 3127: + meaning = "网络连接失败"; + causeAndSolution = "网络不稳定。测试网络连接,重启路由器,或联系网络提供商。"; + break; + } + break; + + case 115: // nn::mii (Mii 相关) + switch (descriptionId) + { + case 96: + meaning = "Mii 数据错误"; + causeAndSolution = "Mii 数据损坏。删除并重新创建 Mii,或更新系统。"; + break; + } + break; + + case 139: // nn::nfp (NFC/Amiibo) + switch (descriptionId) + { + case 6: + meaning = "Amiibo 读取错误"; + causeAndSolution = "Amiibo 读取失败。检查 Amiibo 是否损坏,更新系统,或尝试其他 Amiibo。"; + break; + } + break; + + case 153: // nn::ir (红外摄像头) + switch (descriptionId) + { + case 321: + meaning = "红外摄像头读取错误"; + causeAndSolution = "红外摄像头无法读取。检查摄像头是否被遮挡,清洁镜头,或联系任天堂支持。"; + break; + case 1540: + meaning = "红外摄像头硬件错误"; + causeAndSolution = "红外摄像头硬件故障。联系任天堂支持进行维修。"; + break; + } + break; + + case 155: // nn::account (账户服务) + switch (descriptionId) + { + case 8006: + meaning = "无法链接任天堂账户"; + causeAndSolution = "网络问题或服务中断。检查网络连接,稍后重试,或查看任天堂网络状态页面。"; + break; + } + break; + + case 160: // nn::online (在线服务) + switch (descriptionId) + { + case 103: + meaning = "无法加入在线匹配"; + causeAndSolution = "网络不稳定。重启 Switch,检查网络连接,或稍后重试。"; + break; + case 202: + meaning = "无法连接到在线服务"; + causeAndSolution = "服务可能正在维护。查看任天堂网络状态页面,稍后重试。"; + break; + case 8006: + meaning = "连接测试失败"; + causeAndSolution = "网络问题。重启路由器,检查网络设置,或尝试其他网络。"; + break; + } + break; + + case 162: // nn::application (应用程序) + switch (descriptionId) + { + case 2: + meaning = "软件崩溃"; + causeAndSolution = "软件崩溃,可能是盗版内容或固件问题。删除盗版内容,更新系统,或重新安装软件。"; + break; + case 101: + meaning = "软件需要更新"; + causeAndSolution = "游戏或软件需要更新。检查软件更新并安装。"; + break; + } + break; + + case 168: // nn::sys (系统) + switch (descriptionId) + { + case 0: + meaning = "需要软件更新"; + causeAndSolution = "软件需要更新。更新游戏或系统固件。"; + break; + case 2: + meaning = "系统崩溃"; + causeAndSolution = "系统崩溃,可能是硬件问题。重启 Switch,若无效联系任天堂支持。"; + break; + } + break; + + case 205: // nn::camera (摄像头) + switch (descriptionId) + { + case 123: + meaning = "摄像头读取错误"; + causeAndSolution = "摄像头无法读取。检查摄像头是否被遮挡,清洁镜头,或联系任天堂支持。"; + break; + } + break; + + case 306: // nn::ngc (网络游戏连接) + switch (descriptionId) + { + case 501: + meaning = "无法加入在线匹配"; + causeAndSolution = "网络连接中断。重启 Switch,检查网络连接,或稍后重试。"; + break; + case 502: + meaning = "匹配过程失败"; + causeAndSolution = "网络不稳定。测试网络连接,重启路由器,或联系网络提供商。"; + break; + case 820: + meaning = "在线服务不可用"; + causeAndSolution = "服务可能正在维护。查看任天堂网络状态页面,稍后重试。"; + break; + } + break; + + case 613: // nn::eShop (eShop) + switch (descriptionId) + { + case 1400: + meaning = "无法使用信用卡购买"; + causeAndSolution = "信用卡信息错误或 eShop 服务问题。检查信用卡信息,稍后重试,或联系任天堂支持。"; + break; + case 6838: + meaning = "eShop 连接失败"; + causeAndSolution = "网络问题或 eShop 维护。检查网络连接,查看任天堂网络状态页面,稍后重试。"; + break; + } + break; + + case 618: // nn::ngc (网络游戏连接) + switch (descriptionId) + { + case 6: + meaning = "无法加入在线匹配"; + causeAndSolution = "网络不稳定。重启 Switch,检查网络连接,或稍后重试。"; + break; + case 201: + meaning = "匹配已满"; + causeAndSolution = "尝试加入的匹配已满。稍后重试,或加入其他匹配。"; + break; + case 501: + meaning = "匹配过程失败"; + causeAndSolution = "网络连接中断。重启 Switch,检查网络连接,或稍后重试。"; + break; + } + break; + + case 801: // nn::sns (社交网络服务) + switch (descriptionId) + { + case 7002: + meaning = "无法上传截图到 Twitter"; + causeAndSolution = "服务可能正在维护。查看任天堂网络状态页面,稍后重试。"; + break; + case 7199: + meaning = "无法上传照片到 Facebook"; + causeAndSolution = "服务可能正在维护。查看任天堂网络状态页面,稍后重试。"; + break; + } + break; + + case 810: // nn::account (账户服务) + switch (descriptionId) + { + case 1224: + meaning = "无法登录任天堂账户"; + causeAndSolution = "网络问题或服务中断。检查网络连接,稍后重试,或查看任天堂网络状态页面。"; + break; + case 1500: + meaning = "无法登录 Facebook 账户"; + causeAndSolution = "服务可能正在维护。重启 Switch,稍后重试。"; + break; + } + break; + + case 811: // nn::account (账户服务) + switch (descriptionId) + { + case 1006: + meaning = "无法链接任天堂账户"; + causeAndSolution = "网络问题或 DNS 错误。检查网络连接,尝试更换 DNS(如 8.8.8.8),或稍后重试。"; + break; + case 5001: + meaning = "无法访问 eShop"; + causeAndSolution = "eShop 服务中断。查看任天堂网络状态页面,稍后重试。"; + break; + } + break; + + case 813: // nn::eShop (eShop) + switch (descriptionId) + { + case 0: + meaning = "eShop 访问失败"; + causeAndSolution = "eShop 服务中断。查看任天堂网络状态页面,稍后重试。"; + break; + case 2470: + meaning = "交易处理失败"; + causeAndSolution = "信用卡信息错误或 eShop 服务问题。检查信用卡信息,稍后重试,或联系任天堂支持。"; + break; + } + break; + + case 819: // nn::online (在线服务) + switch (descriptionId) + { + case 3: + meaning = "软件被暂停"; + causeAndSolution = "同一账户在另一台设备上使用。关闭其他设备上的软件,或使用不同账户。"; + break; + } + break; + + default: + meaning = "未知模块"; + causeAndSolution = "未识别的模块 ID,请检查日志或联系任天堂支持。"; + break; + } + + return $"错误码: {errorCode}\n含义: {meaning}\n可能原因及解决办法: {causeAndSolution}"; + } +} diff --git a/Assets/Scripts/AxiNSApi/AxiNSErrCode.cs.meta b/Assets/Scripts/AxiNSApi/AxiNSErrCode.cs.meta new file mode 100644 index 0000000..d734ad7 --- /dev/null +++ b/Assets/Scripts/AxiNSApi/AxiNSErrCode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0e28b69692cb1bb4a9d8ddb91274fa50 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AxiNSApi/AxiNSIO.cs b/Assets/Scripts/AxiNSApi/AxiNSIO.cs new file mode 100644 index 0000000..7e20444 --- /dev/null +++ b/Assets/Scripts/AxiNSApi/AxiNSIO.cs @@ -0,0 +1,568 @@ +#if UNITY_SWITCH +using nn.fs; +using System.Security.Cryptography; + +#endif + +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 + /// + /// 检查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(); + //这个异常捕获。真的别扭 + return nn.fs.FileSystem.ResultPathAlreadyExists.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 filePath) + { +#if !UNITY_SWITCH + return false; +#else + // 使用封装函数检查和创建父目录 + if (!EnsureParentDirectory(filePath, true)) + { + UnityEngine.Debug.LogError($"无法确保父目录,文件写入取消: {filePath}"); + return false; + } + return true; +#endif + } + /// + /// 保存并创建文件(如果目录不存在回先自动创建目录) + /// + /// + /// + /// + public bool FileToSaveWithCreate(string filePath, System.IO.MemoryStream ms) + { + return FileToSaveWithCreate(filePath, ms.ToArray()); + } + /// + /// 保存并创建文件(如果目录不存在回先自动创建目录) + /// + /// + /// + /// + public bool FileToSaveWithCreate(string filePath, byte[] data) + { +#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); + + //必须得提交,否则没有真实写入 + result = FileSystem.Commit(save_name); + //result.abortUnlessSuccess(); + if (!result.IsSuccess()) + { + UnityEngine.Debug.LogError($"FileSystem.Commit({save_name}) 失败: " + result.GetErrorInfo()); + return false; + } + UnityEngine.Debug.Log($"FileSystem.Commit({save_name}) 成功: "); + + +#if UNITY_SWITCH && !UNITY_EDITOR + // 停止阻止用户退出游戏 + UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection(); +#endif + + return true; +#endif + } + 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 + 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 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; + } + result = nn.fs.FileSystem.Commit(save_name); + if (!result.IsSuccess()) + { + UnityEngine.Debug.LogError($"FileSystem.Commit({save_name}) 失败: " + result.GetErrorInfo()); + return false; + } + return true; + +#if UNITY_SWITCH && !UNITY_EDITOR + // End preventing the user from quitting the game while saving. + UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection(); +#endif + +#endif + } + 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; + } + result = nn.fs.FileSystem.Commit(save_name); + if (!result.IsSuccess()) + { + UnityEngine.Debug.LogError($"FileSystem.Commit({save_name}) 失败: " + result.GetErrorInfo()); + return false; + } + return true; + +#if UNITY_SWITCH && !UNITY_EDITOR + // End preventing the user from quitting the game while saving. + UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection(); +#endif +#endif + } + + /// + /// 递归删除目录 + /// + /// + /// + 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; + } + result = nn.fs.FileSystem.Commit(save_name); + if (!result.IsSuccess()) + { + UnityEngine.Debug.LogError($"FileSystem.Commit({save_name}) 失败: " + result.GetErrorInfo()); + return false; + } + return true; + +#if UNITY_SWITCH && !UNITY_EDITOR + // End preventing the user from quitting the game while saving. + UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection(); +#endif +#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; + } + result = nn.fs.FileSystem.Commit(save_name); + if (!result.IsSuccess()) + { + UnityEngine.Debug.LogError($"FileSystem.Commit({save_name}) 失败: " + result.GetErrorInfo()); + return false; + } + return true; + +#if UNITY_SWITCH && !UNITY_EDITOR + // End preventing the user from quitting the game while saving. + UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection(); +#endif +#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; + } + result = nn.fs.FileSystem.Commit(save_name); + if (!result.IsSuccess()) + { + UnityEngine.Debug.LogError($"FileSystem.Commit({save_name}) 失败: " + result.GetErrorInfo()); + return false; + } + return true; + +#if UNITY_SWITCH && !UNITY_EDITOR + // End preventing the user from quitting the game while saving. + UnityEngine.Switch.Notification.LeaveExitRequestHandlingSection(); +#endif +#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 + } + /// + /// 检查指定挂载点是否可访问 + /// + /// 路径前缀,例如 "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 + } +} diff --git a/Assets/Scripts/AxiNSApi/AxiNSIO.cs.meta b/Assets/Scripts/AxiNSApi/AxiNSIO.cs.meta new file mode 100644 index 0000000..7fb9bc0 --- /dev/null +++ b/Assets/Scripts/AxiNSApi/AxiNSIO.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d910a015a6b6561418bdff7f2c48cffa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AxiNSApi/AxiNSMount.cs b/Assets/Scripts/AxiNSApi/AxiNSMount.cs new file mode 100644 index 0000000..a634a43 --- /dev/null +++ b/Assets/Scripts/AxiNSApi/AxiNSMount.cs @@ -0,0 +1,134 @@ +#if UNITY_SWITCH +using nn.account; +#endif +public class AxiNSMount +{ + static bool bInMount = false; + internal static string m_SaveMountName; + static bool bInSdCardMount = false; + internal static string m_SdCardMountName; + static bool bInSdCardDebugMount = false; + internal static string m_SdCardDebugMountName; + + public bool SaveIsMount => bInMount; + public string SaveMountName + { + get + { + if (!bInMount) + return string.Empty; + else + return m_SaveMountName; + } + } + +#if UNITY_SWITCH + public bool MountSave(Uid userId, string mountName = "save") + { + if (bInMount) + return true; + + if (!nn.fs.SaveData.IsExisting(userId)) + { + UnityEngine.Debug.LogError($"{userId.ToString()}存档不存在!"); + return false; + } + UnityEngine.Debug.Log($"{userId.ToString()}存档确保存在!"); + + nn.Result result; + result = nn.fs.SaveData.Mount(mountName, userId); + //result.abortUnlessSuccess(); + + if (!result.IsSuccess()) + { + UnityEngine.Debug.LogError($"MountSave->挂载{mountName}:/ 失败: " + result.ToString()); + return false; + } + UnityEngine.Debug.Log($"MountSave->挂载{mountName}:/ 成功 "); + m_SaveMountName = mountName; + bInMount = true; + return true; + } +#endif + + public bool MountSDForDebug(string mountName = "dbgsd") + { +#if !UNITY_SWITCH + return false; +#else + if (bInSdCardDebugMount) + return true; + nn.Result result; + result = nn.fs.SdCard.MountForDebug(mountName); + //result.abortUnlessSuccess(); + if (!result.IsSuccess()) + { + UnityEngine.Debug.LogError($"nn_fs_MountSdCardForDebug->挂载{mountName}:/ 失败: " + result.ToString()); + return false; + } + UnityEngine.Debug.Log($"nn_fs_MountSdCardForDebug->挂载{mountName}:/ 成功 "); + m_SdCardDebugMountName = mountName; + bInSdCardDebugMount = true; + return true; +#endif + } + public bool MountSD(string mountName = "sd") + { +#if !UNITY_SWITCH + return false; +#else + if (bInSdCardMount) + return true; + nn.Result result; + result = AxiNSSDCard.Mount(mountName); + if (!result.IsSuccess()) + { + UnityEngine.Debug.LogError($"nn_fs_MountSdCard->挂载{mountName}:/ 失败: " + result.ToString()); + return false; + } + UnityEngine.Debug.Log($"nn_fs_MountSdCard->挂载{mountName}:/ 成功 "); + m_SdCardMountName = mountName; + bInSdCardMount = true; + return true; +#endif + } + public void UnmountSave() + { +#if UNITY_SWITCH + if (!bInMount) + { + UnityEngine.Debug.LogError($"{m_SaveMountName}:/ 没有被挂载,无需卸载"); + return; + } + nn.fs.FileSystem.Unmount(m_SaveMountName); + UnityEngine.Debug.LogError($"UnmountSaveForDebufa->已卸载{m_SaveMountName}:/ "); + bInMount = false; +#endif + } + public void UnmountSDCardForDebug() + { +#if UNITY_SWITCH + if (!bInSdCardDebugMount) + { + UnityEngine.Debug.LogError($"{m_SdCardDebugMountName}:/ 没有被挂载,无需卸载"); + return; + } + nn.fs.FileSystem.Unmount(m_SdCardDebugMountName); + UnityEngine.Debug.LogError($"UnmountSDCardForDebug->已卸载{m_SdCardDebugMountName}:/ "); + bInSdCardDebugMount = false; +#endif + } + public void UnmountSDCard() + { +#if UNITY_SWITCH + if (!bInSdCardMount) + { + UnityEngine.Debug.LogError($"{m_SdCardMountName}:/ 没有被挂载,无需卸载"); + return; + } + nn.fs.FileSystem.Unmount(m_SdCardMountName); + UnityEngine.Debug.LogError($"UnmountSDCard->已卸载{m_SdCardMountName}:/ "); + bInSdCardMount = false; +#endif + } +} \ No newline at end of file diff --git a/Assets/Scripts/AxiNSApi/AxiNSMount.cs.meta b/Assets/Scripts/AxiNSApi/AxiNSMount.cs.meta new file mode 100644 index 0000000..6af050f --- /dev/null +++ b/Assets/Scripts/AxiNSApi/AxiNSMount.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 48826c5dc8959ff4db8c6a51b6568bb7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AxiNSApi/AxiNSSDCard.cs b/Assets/Scripts/AxiNSApi/AxiNSSDCard.cs new file mode 100644 index 0000000..f1fe28e --- /dev/null +++ b/Assets/Scripts/AxiNSApi/AxiNSSDCard.cs @@ -0,0 +1,23 @@ +#if UNITY_SWITCH +using nn.account; +#endif +public class AxiNSSDCard +{ +#if UNITY_SWITCH + +#if DEVELOPMENT_BUILD || NN_FS_SD_CARD_FOR_DEBUG_ENABLE + [DllImport(Nn.DllName, + CallingConvention = CallingConvention.Cdecl, + EntryPoint = "nn_fs_MountSdCard")] + public static extern nn.Result Mount(string name); +#else + + public static nn.Result Mount(string name) + { + return new nn.Result(); + } +#endif + + +#endif +} \ No newline at end of file diff --git a/Assets/Scripts/AxiNSApi/AxiNSSDCard.cs.meta b/Assets/Scripts/AxiNSApi/AxiNSSDCard.cs.meta new file mode 100644 index 0000000..1b9591a --- /dev/null +++ b/Assets/Scripts/AxiNSApi/AxiNSSDCard.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 21fa04ba4da10d74aafd65dd138478b7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AxiNSApi/AxiNSUser.cs b/Assets/Scripts/AxiNSApi/AxiNSUser.cs new file mode 100644 index 0000000..2deaf88 --- /dev/null +++ b/Assets/Scripts/AxiNSApi/AxiNSUser.cs @@ -0,0 +1,106 @@ +#if UNITY_SWITCH +using nn.account; +#endif + +public class AxiNSUser +{ + bool m_bInit = false; + bool m_bGotOpenPreselectedUser = false; + +#if UNITY_SWITCH + Uid m_UserId; + nn.account.UserHandle mUserHandle; + nn.account.Nickname m_NickName; +#endif + + #region 对外公开接口,确保内部安全处理,避免崩溃 + +#if UNITY_SWITCH + public bool GetUserID(out Uid uid) + { + InitPreselectedUserInfo(); + if (!m_bGotOpenPreselectedUser) + { + uid = Uid.Invalid; + return false; + } + uid = m_UserId; + return true; + } +#endif + public bool GetNickName(out string NickName) + { +#if !UNITY_SWITCH + NickName = ""; + return true; +#else + InitPreselectedUserInfo(); + if (!m_bGotOpenPreselectedUser) + { + NickName = string.Empty; + return false; + } + NickName = m_NickName.ToString(); + return true; +#endif + } +#endregion + /// + /// 初始化Account模块儿 + /// + void InitNSAccount() + { +#if UNITY_SWITCH + if (m_bInit) + return; + //必须先初始化NS的Account 不然调用即崩 + nn.account.Account.Initialize(); + m_bInit = true; +#endif + } + + /// + /// 获取预选用户 + /// + void InitPreselectedUserInfo() + { +#if UNITY_SWITCH + if (m_bGotOpenPreselectedUser) + return; + + InitNSAccount(); + nn.Result result; + mUserHandle = new nn.account.UserHandle(); + if (!nn.account.Account.TryOpenPreselectedUser(ref mUserHandle)) + { + UnityEngine.Debug.LogError("打开预选的用户失败."); + return; + } + UnityEngine.Debug.Log("打开预选用户成功."); + result = nn.account.Account.GetUserId(ref m_UserId, mUserHandle); + //result.abortUnlessSuccess(); + if (!result.IsSuccess()) + { + UnityEngine.Debug.LogError($"GetUserId失败: {result.ToString()}"); + return; + } + + if (m_UserId == Uid.Invalid) + { + UnityEngine.Debug.LogError("无法获取用户 ID"); + return; + } + UnityEngine.Debug.Log($"获取用户 ID:{m_UserId.ToString()}"); + + result = nn.account.Account.GetNickname(ref m_NickName, m_UserId); + //result.abortUnlessSuccess(); + if (!result.IsSuccess()) + { + UnityEngine.Debug.LogError($"GetNickname失败: {result.ToString()}"); + return; + } + UnityEngine.Debug.Log($"获取用户 NickName ID:{m_NickName.ToString()}"); + m_bGotOpenPreselectedUser = true; +#endif + } +} diff --git a/Assets/Scripts/AxiNSApi/AxiNSUser.cs.meta b/Assets/Scripts/AxiNSApi/AxiNSUser.cs.meta new file mode 100644 index 0000000..af80767 --- /dev/null +++ b/Assets/Scripts/AxiNSApi/AxiNSUser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95c2e164c69c6cc4887a194d6eba0cc2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: