diff --git a/AxibugEmuOnline.Client/Assets/AxiProjectTools/AxiNSPack.meta b/AxibugEmuOnline.Client/Assets/AxiProjectTools/AxiNSPack.meta index 7182dbd8..b3e69672 100644 --- a/AxibugEmuOnline.Client/Assets/AxiProjectTools/AxiNSPack.meta +++ b/AxibugEmuOnline.Client/Assets/AxiProjectTools/AxiNSPack.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 611bc182f939ea147a72b08613e2d2ba +guid: 164952f99969ca942b4761b200d7e381 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/AxibugEmuOnline.Client/Assets/AxiProjectTools/AxiNSPack/Editors/AxibugNSPTools.cs b/AxibugEmuOnline.Client/Assets/AxiProjectTools/AxiNSPack/Editors/AxibugNSPTools.cs index c9e192f0..e623c351 100644 --- a/AxibugEmuOnline.Client/Assets/AxiProjectTools/AxiNSPack/Editors/AxibugNSPTools.cs +++ b/AxibugEmuOnline.Client/Assets/AxiProjectTools/AxiNSPack/Editors/AxibugNSPTools.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using UnityEditor; @@ -82,7 +83,7 @@ namespace AxibugEmuOnline.Editors { BuildReport report = BuildPipeline.BuildPlayer(options); } - catch(Exception ex) + catch (Exception ex) { Debug.LogError($"[AxibugNSPTools] Unity Build NSP 错误:{ex.ToString()}"); return; @@ -147,13 +148,13 @@ namespace AxibugEmuOnline.Editors #region 清理临时目录 CleanDirectory(Path.Combine(nspParentDir, "repacker_extract")); CleanDirectory(Path.Combine(Path.GetTempPath(), "NCA")); - CleanDirectory(Path.Combine(WorkRoot, "hacpack_backup")); + CleanDirectory(Path.Combine(nspParentDir, "hacpack_backup")); #endregion EditorUtility.DisplayProgressBar("AxibugNSPTools", $"解包NSP文件", 0.2f); #region 解包NSP文件 string extractPath = Path.Combine(nspParentDir, "repacker_extract"); - ExecuteCommand($"{tools["authoringTool"]} extract -o \"{extractPath}\" \"{nspFilePath}\""); + ExecuteCommand($"{tools["authoringTool"]} extract -o \"{extractPath}\" \"{nspFilePath}\"", nspParentDir); string controlPath = null; string programPath = null; @@ -167,34 +168,43 @@ namespace AxibugEmuOnline.Editors #region 重建NCA/NSP string tmpPath = Path.Combine(Path.GetTempPath(), "NCA"); - EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建NCA", 0.6f); - string programNCA = BuildProgramNCA(tmpPath, titleID, programPath); - EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建NCA", 0.7f); - string controlNCA = BuildControlNCA(tmpPath, titleID, controlPath); - EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建NCA", 0.8f); - BuildMetaNCA(tmpPath, titleID, programNCA, controlNCA); + EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建 Program NCA", 0.3f); + string programNCA = BuildProgramNCA(tmpPath, titleID, programPath, nspParentDir); + EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建 Control NCA", 0.4f); + string controlNCA = BuildControlNCA(tmpPath, titleID, controlPath, nspParentDir); + EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建 Meta NCA", 0.5f); + BuildMetaNCA(tmpPath, titleID, programNCA, controlNCA, nspParentDir); + EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建NSP", 0.6f); + string outputNSP = BuildFinalNSP(nspFilePath, nspParentDir, tmpPath, titleID, nspParentDir); EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建NSP", 0.9f); - string outputNSP = BuildFinalNSP(nspFilePath, nspParentDir, tmpPath, titleID); - EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建NSP", 1f); Debug.Log($"[AxibugNSPTools]Repacking completed: {outputNSP}"); - EditorUtility.ClearProgressBar(); - #endregion + + #endregion + + EditorUtility.DisplayProgressBar("AxibugNSPTools", $"清理临时目录", 1); + #region 清理临时目录 + CleanDirectory(Path.Combine(nspParentDir, "repacker_extract")); + CleanDirectory(Path.Combine(Path.GetTempPath(), "NCA")); + CleanDirectory(Path.Combine(nspParentDir, "hacpack_backup")); + #endregion + System.Diagnostics.Process.Start("explorer", "/select,\"" + outputNSP.Trim() + "\""); + EditorUtility.ClearProgressBar(); } + + #region 辅助方法 static string GetUserInput() { Console.Write("Enter the NSP filepath: "); return Console.ReadLine(); } - static string ExtractTitleID(string path) { var match = Regex.Match(path, @"0100[\dA-Fa-f]{12}"); return match.Success ? match.Value : null; } - static void CleanDirectory(string path) { if (Directory.Exists(path)) @@ -215,8 +225,9 @@ namespace AxibugEmuOnline.Editors } } - static string ExecuteCommand(string command) + static string ExecuteCommand(string command, string workdir) { + Debug.Log($"调用cmd=>{command}"); var process = new System.Diagnostics.Process() { StartInfo = new System.Diagnostics.ProcessStartInfo @@ -228,7 +239,8 @@ namespace AxibugEmuOnline.Editors UseShellExecute = false, CreateNoWindow = true, StandardOutputEncoding = Encoding.UTF8, // 明确指定编码 - StandardErrorEncoding = Encoding.UTF8 + StandardErrorEncoding = Encoding.UTF8, + WorkingDirectory = workdir } }; @@ -275,36 +287,36 @@ namespace AxibugEmuOnline.Editors #endregion #region NCA构建逻辑 - static string BuildProgramNCA(string tmpPath, string titleID, string programDir) + static string BuildProgramNCA(string tmpPath, string titleID, string programDir, string workdir) { string args = $"-k \"{prodKeysPath}\" -o \"{tmpPath}\" --titleid {titleID} " + $"--type nca --ncatype program --exefsdir \"{programDir}/fs0\" " + $"--romfsdir \"{programDir}/fs1\" --logodir \"{programDir}/fs2\""; - string output = ExecuteCommand($"{tools["hacPack"]} {args}"); + string output = ExecuteCommand($"{tools["hacPack"]} {args}", workdir); return ParseNCAOutput(output, "Program"); } - static string BuildControlNCA(string tmpPath, string titleID, string controlDir) + static string BuildControlNCA(string tmpPath, string titleID, string controlDir, string workdir) { string args = $"-k \"{prodKeysPath}\" -o \"{tmpPath}\" --titleid {titleID} " + $"--type nca --ncatype control --romfsdir \"{controlDir}/fs0\""; - string output = ExecuteCommand($"{tools["hacPack"]} {args}"); + string output = ExecuteCommand($"{tools["hacPack"]} {args}", workdir); return ParseNCAOutput(output, "Control"); } - static void BuildMetaNCA(string tmpPath, string titleID, string programNCA, string controlNCA) + static void BuildMetaNCA(string tmpPath, string titleID, string programNCA, string controlNCA, string workdir) { string args = $"-k \"{prodKeysPath}\" -o \"{tmpPath}\" --titleid {titleID} " + $"--type nca --ncatype meta --titletype application " + $"--programnca \"{programNCA}\" --controlnca \"{controlNCA}\""; - ExecuteCommand($"{tools["hacPack"]} {args}"); + ExecuteCommand($"{tools["hacPack"]} {args}", workdir); } - static string BuildFinalNSP(string origPath, string parentDir, string tmpPath, string titleID) + static string BuildFinalNSP(string origPath, string parentDir, string tmpPath, string titleID, string workdir) { string outputPath = origPath.Replace(".nsp", "_repacked.nsp"); if (File.Exists(outputPath)) File.Delete(outputPath); @@ -312,7 +324,7 @@ namespace AxibugEmuOnline.Editors string args = $"-k \"{prodKeysPath}\" -o \"{parentDir}\" --titleid {titleID} " + $"--type nsp --ncadir \"{tmpPath}\""; - ExecuteCommand($"{tools["hacPack"]} {args}"); + ExecuteCommand($"{tools["hacPack"]} {args}", workdir); File.Move(Path.Combine(parentDir, $"{titleID}.nsp"), outputPath); return outputPath; } @@ -321,7 +333,9 @@ namespace AxibugEmuOnline.Editors { var line = output.Split('\n') .FirstOrDefault(l => l.Contains($"Created {type} NCA:")); - return line?.Split(':').Last().Trim(); + //return line?.Split(':').Last().Trim(); + return line?.Substring(line.IndexOf("NCA:") + "NCA:".Length).Trim(); + } #endregion } diff --git a/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSIO.cs b/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSIO.cs index 6431408c..7e20444b 100644 --- a/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSIO.cs +++ b/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSIO.cs @@ -1,5 +1,7 @@ #if UNITY_SWITCH using nn.fs; +using System.Security.Cryptography; + #endif public class AxiNSIO @@ -253,18 +255,194 @@ public class AxiNSIO #if !UNITY_SWITCH return false; #else - //TODO + + +#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 - } - public bool DeletePathDir(string filename) + + 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 - //TODO + +#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 - } - bool EnsureParentDirectory(string filePath, bool bAutoCreateDir = true) + + 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; diff --git a/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSMount.cs b/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSMount.cs index 0d1bae7c..a634a432 100644 --- a/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSMount.cs +++ b/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSMount.cs @@ -5,10 +5,12 @@ public class AxiNSMount { static bool bInMount = false; internal static string m_SaveMountName; - static bool bInMountForDebug = false; - internal static string m_SaveMountForDebugName; + static bool bInSdCardMount = false; + internal static string m_SdCardMountName; + static bool bInSdCardDebugMount = false; + internal static string m_SdCardDebugMountName; - public bool SaveIsMount => bInMount; + public bool SaveIsMount => bInMount; public string SaveMountName { get @@ -47,14 +49,14 @@ public class AxiNSMount bInMount = true; return true; } - #endif +#endif - public bool MountSDForDebug(string mountName = "sd") + public bool MountSDForDebug(string mountName = "dbgsd") { #if !UNITY_SWITCH return false; #else - if (bInMountForDebug) + if (bInSdCardDebugMount) return true; nn.Result result; result = nn.fs.SdCard.MountForDebug(mountName); @@ -65,35 +67,31 @@ public class AxiNSMount return false; } UnityEngine.Debug.Log($"nn_fs_MountSdCardForDebug->挂载{mountName}:/ 成功 "); - m_SaveMountForDebugName = mountName; - bInMountForDebug = true; + m_SdCardDebugMountName = mountName; + bInSdCardDebugMount = true; return true; #endif } - public bool MountSD(string mountName = "sd") { #if !UNITY_SWITCH return false; #else - if (bInMountForDebug) + if (bInSdCardMount) return true; nn.Result result; - result = nn.fs.SdCard.Mount(mountName); - //result.abortUnlessSuccess(); + 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_SaveMountForDebugName = mountName; - bInMountForDebug = true; + m_SdCardMountName = mountName; + bInSdCardMount = true; return true; #endif } - - public void UnmountSave() { #if UNITY_SWITCH @@ -107,18 +105,30 @@ public class AxiNSMount bInMount = false; #endif } - - public void UnmountSaveForDebug() + public void UnmountSDCardForDebug() { #if UNITY_SWITCH - if (!bInMountForDebug) + if (!bInSdCardDebugMount) { - UnityEngine.Debug.LogError($"{m_SaveMountForDebugName}:/ 没有被挂载,无需卸载"); + UnityEngine.Debug.LogError($"{m_SdCardDebugMountName}:/ 没有被挂载,无需卸载"); return; } - nn.fs.FileSystem.Unmount(m_SaveMountForDebugName); - UnityEngine.Debug.LogError($"UnmountSaveForDebufa->已卸载{m_SaveMountForDebugName}:/ "); - bInMountForDebug = false; + 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/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSSDCard.cs b/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSSDCard.cs new file mode 100644 index 00000000..f1fe28e6 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/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/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSSDCard.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/AxiNSApi/AxiNSSDCard.cs.meta new file mode 100644 index 00000000..1b9591a5 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/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: