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: