引入 AxiNSApi 方便操作Switch的API 做一些安全封装
This commit is contained in:
parent
4b0a781957
commit
5014c81539
AxibugEmuOnline.Client/Assets/Plugins
8
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi.meta
Normal file
8
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4094856a5db24f142a14fda03f1215a2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
39
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNS.cs
Normal file
39
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNS.cs
Normal file
@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化(最好在项目第一时间初始化,保证先初始化再使用某些东西,才不闪退)
|
||||
/// </summary>
|
||||
public void Init()
|
||||
{
|
||||
#if UNITY_SWITCH
|
||||
if (!user.GetUserID(out Uid uid))
|
||||
return;
|
||||
mount.MountSave(uid);
|
||||
#endif
|
||||
}
|
||||
}
|
11
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNS.cs.meta
Normal file
11
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNS.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52541c757d45c4c488726bcc39f73ba6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
367
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSErrCode.cs
Normal file
367
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSErrCode.cs
Normal file
@ -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
|
||||
/// <summary>
|
||||
/// 根据模块 ID 和描述 ID 返回任天堂 Switch 错误码的含义、可能原因及解决办法。
|
||||
/// </summary>
|
||||
/// <param name="moduleId">模块 ID</param>
|
||||
/// <param name="descriptionId">描述 ID</param>
|
||||
/// <returns>包含错误码、含义、可能原因及解决办法的字符串</returns>
|
||||
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}";
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e28b69692cb1bb4a9d8ddb91274fa50
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
394
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSIO.cs
Normal file
394
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSIO.cs
Normal file
@ -0,0 +1,394 @@
|
||||
#if UNITY_SWITCH
|
||||
using nn.fs;
|
||||
#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
|
||||
/// <summary>
|
||||
/// 检查Path是否存在
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <returns></returns>
|
||||
public bool CheckPathExists(string filePath)
|
||||
{
|
||||
#if !UNITY_SWITCH
|
||||
return false;
|
||||
#else
|
||||
nn.fs.EntryType entryType = 0;
|
||||
nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, filePath);
|
||||
//result.abortUnlessSuccess();
|
||||
//这个异常捕获。真的别扭
|
||||
return nn.fs.FileSystem.ResultPathAlreadyExists.Includes(result);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// 检查Path是否不存在
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <returns></returns>
|
||||
public bool CheckPathNotFound(string filePath)
|
||||
{
|
||||
#if !UNITY_SWITCH
|
||||
return false;
|
||||
#else
|
||||
nn.fs.EntryType entryType = 0;
|
||||
nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, filePath);
|
||||
//这个异常捕获。真的别扭
|
||||
return nn.fs.FileSystem.ResultPathNotFound.Includes(result);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// 创建目录,目录存在也会返回true
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <returns></returns>
|
||||
public bool CreateDir(string filePath)
|
||||
{
|
||||
#if !UNITY_SWITCH
|
||||
return false;
|
||||
#else
|
||||
// 使用封装函数检查和创建父目录
|
||||
if (!EnsureParentDirectory(filePath, true))
|
||||
{
|
||||
UnityEngine.Debug.LogError($"无法确保父目录,文件写入取消: {filePath}");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// 保存并创建文件(如果目录不存在回先自动创建目录)
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="bw"></param>
|
||||
/// <returns></returns>
|
||||
public bool FileToSaveWithCreate(string filePath, System.IO.MemoryStream ms)
|
||||
{
|
||||
return FileToSaveWithCreate(filePath, ms.ToArray());
|
||||
}
|
||||
/// <summary>
|
||||
/// 保存并创建文件(如果目录不存在回先自动创建目录)
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public 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
|
||||
}
|
||||
/// <summary>
|
||||
/// 保存并创建文件(如果目录不存在回先自动创建目录)
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="bw"></param>
|
||||
/// <returns></returns>
|
||||
public bool LoadSwitchDataFile(string filename, ref System.IO.MemoryStream ms)
|
||||
{
|
||||
if (LoadSwitchDataFile(filename, out byte[] outputData))
|
||||
{
|
||||
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
|
||||
//TODO
|
||||
#endif
|
||||
}
|
||||
public bool DeletePathDir(string filename)
|
||||
{
|
||||
#if !UNITY_SWITCH
|
||||
return false;
|
||||
#else
|
||||
//TODO
|
||||
#endif
|
||||
}
|
||||
bool EnsureParentDirectory(string filePath, bool bAutoCreateDir = true)
|
||||
{
|
||||
#if !UNITY_SWITCH
|
||||
return false;
|
||||
#else
|
||||
// 参数校验
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
UnityEngine.Debug.LogError($"无效参数:filePath={filePath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 提取路径前缀(如 save:/、sd:/)
|
||||
int prefixEndIndex = filePath.IndexOf(":/");
|
||||
if (prefixEndIndex == -1)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"文件路径 {filePath} 格式无效,未找到 ':/' 前缀");
|
||||
return false;
|
||||
}
|
||||
string pathPrefix = filePath.Substring(0, prefixEndIndex + 2); // 提取前缀,例如 "save:/"
|
||||
string relativePath = filePath.Substring(prefixEndIndex + 2); // 移除前缀,得到相对路径
|
||||
|
||||
// 检查挂载状态
|
||||
if (!IsMountPointAccessible(pathPrefix))
|
||||
{
|
||||
UnityEngine.Debug.LogError($"挂载点 {pathPrefix} 未挂载,无法操作路径 {filePath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 提取父目录路径
|
||||
string directoryPath = System.IO.Path.GetDirectoryName(relativePath); // 获取父目录相对路径
|
||||
if (string.IsNullOrEmpty(directoryPath))
|
||||
{
|
||||
UnityEngine.Debug.Log($"文件路径 {filePath} 无需创建父目录(位于根目录)");
|
||||
return true; // 根目录无需创建
|
||||
}
|
||||
|
||||
string fullDirectoryPath = $"{pathPrefix}{directoryPath}"; // 拼接完整父目录路径
|
||||
UnityEngine.Debug.Log($"检查父目录: {fullDirectoryPath}");
|
||||
|
||||
// 检查路径是否存在及其类型
|
||||
nn.fs.EntryType entryType = 0;
|
||||
nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, fullDirectoryPath);
|
||||
if (!result.IsSuccess() && nn.fs.FileSystem.ResultPathNotFound.Includes(result))
|
||||
{
|
||||
if (bAutoCreateDir)
|
||||
{
|
||||
// 路径不存在,尝试创建
|
||||
UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 不存在,尝试创建 (判断依据 result=>{result.ToString()})");
|
||||
result = nn.fs.Directory.Create(fullDirectoryPath);
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
UnityEngine.Debug.LogError($"创建父目录失败: {result.GetErrorInfo()}");
|
||||
return false;
|
||||
}
|
||||
UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 创建成功");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (result.IsSuccess() && entryType != nn.fs.EntryType.Directory)
|
||||
{
|
||||
// 路径存在,但不是目录
|
||||
UnityEngine.Debug.LogError($"路径 {fullDirectoryPath} 已存在,但不是目录");
|
||||
return false;
|
||||
}
|
||||
else if (!result.IsSuccess())
|
||||
{
|
||||
// 其他错误
|
||||
UnityEngine.Debug.LogError($"检查父目录失败: {result.GetErrorInfo()}");
|
||||
return false;
|
||||
}
|
||||
// 路径存在且是目录
|
||||
UnityEngine.Debug.Log($"父目录 {fullDirectoryPath} 已存在且有效");
|
||||
return true;
|
||||
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// 检查指定挂载点是否可访问
|
||||
/// </summary>
|
||||
/// <param name="pathPrefix">路径前缀,例如 "save:/" 或 "sd:/"</param>
|
||||
/// <returns>挂载点是否可访问</returns>
|
||||
bool IsMountPointAccessible(string pathPrefix)
|
||||
{
|
||||
#if !UNITY_SWITCH
|
||||
return false;
|
||||
#else
|
||||
if (string.IsNullOrEmpty(pathPrefix))
|
||||
{
|
||||
UnityEngine.Debug.LogError($"无效挂载点: {pathPrefix}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据前缀判断挂载点类型并检查挂载状态
|
||||
if (pathPrefix == $"{save_name}:/")
|
||||
{
|
||||
if (!AxiNS.instance.mount.SaveIsMount)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"{save_name}:/ 未挂载");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (pathPrefix == "sd:/")
|
||||
{
|
||||
long freeSpace = 0;
|
||||
// 检查 SD 卡挂载状态(示例,需根据实际实现调整)
|
||||
nn.Result result = nn.fs.FileSystem.GetFreeSpaceSize(ref freeSpace, "sd:/");
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
UnityEngine.Debug.LogError($"sd:/ 未挂载或无法访问: {result.GetErrorInfo()}");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogWarning($"未知挂载点 {pathPrefix},假定已挂载");
|
||||
return true; // 其他挂载点需根据实际需求实现
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d910a015a6b6561418bdff7f2c48cffa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
124
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSMount.cs
Normal file
124
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSMount.cs
Normal file
@ -0,0 +1,124 @@
|
||||
#if UNITY_SWITCH
|
||||
using nn.account;
|
||||
#endif
|
||||
public class AxiNSMount
|
||||
{
|
||||
static bool bInMount = false;
|
||||
internal static string m_SaveMountName;
|
||||
static bool bInMountForDebug = false;
|
||||
internal static string m_SaveMountForDebugName;
|
||||
|
||||
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 = "sd")
|
||||
{
|
||||
#if !UNITY_SWITCH
|
||||
return false;
|
||||
#else
|
||||
if (bInMountForDebug)
|
||||
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_SaveMountForDebugName = mountName;
|
||||
bInMountForDebug = true;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public bool MountSD(string mountName = "sd")
|
||||
{
|
||||
#if !UNITY_SWITCH
|
||||
return false;
|
||||
#else
|
||||
if (bInMountForDebug)
|
||||
return true;
|
||||
nn.Result result;
|
||||
result = nn.fs.SdCard.Mount(mountName);
|
||||
//result.abortUnlessSuccess();
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
UnityEngine.Debug.LogError($"nn_fs_MountSdCard->挂载{mountName}:/ 失败: " + result.ToString());
|
||||
return false;
|
||||
}
|
||||
UnityEngine.Debug.Log($"nn_fs_MountSdCard->挂载{mountName}:/ 成功 ");
|
||||
m_SaveMountForDebugName = mountName;
|
||||
bInMountForDebug = 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 UnmountSaveForDebug()
|
||||
{
|
||||
#if UNITY_SWITCH
|
||||
if (!bInMountForDebug)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"{m_SaveMountForDebugName}:/ 没有被挂载,无需卸载");
|
||||
return;
|
||||
}
|
||||
nn.fs.FileSystem.Unmount(m_SaveMountForDebugName);
|
||||
UnityEngine.Debug.LogError($"UnmountSaveForDebufa->已卸载{m_SaveMountForDebugName}:/ ");
|
||||
bInMountForDebug = false;
|
||||
#endif
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48826c5dc8959ff4db8c6a51b6568bb7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
106
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSUser.cs
Normal file
106
AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSUser.cs
Normal file
@ -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
|
||||
/// <summary>
|
||||
/// 初始化Account模块儿
|
||||
/// </summary>
|
||||
void InitNSAccount()
|
||||
{
|
||||
#if UNITY_SWITCH
|
||||
if (m_bInit)
|
||||
return;
|
||||
//必须先初始化NS的Account 不然调用即崩
|
||||
nn.account.Account.Initialize();
|
||||
m_bInit = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取预选用户
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95c2e164c69c6cc4887a194d6eba0cc2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Reference in New Issue
Block a user