commit 701a645ea4a1d84834d7632e76d741e7c4f38961 Author: sin365 <353374337@qq.com> Date: Tue Mar 11 15:44:27 2025 +0800 鍒濈増褰掓。 diff --git a/Assets/AxiProjectTools/AxiNSPack.meta b/Assets/AxiProjectTools/AxiNSPack.meta new file mode 100644 index 0000000..7182dbd --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 611bc182f939ea147a72b08613e2d2ba +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AxiProjectTools/AxiNSPack/Editors.meta b/Assets/AxiProjectTools/AxiNSPack/Editors.meta new file mode 100644 index 0000000..88093e6 --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack/Editors.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cbe37300d75dbd641be2e6dca83a913c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AxiProjectTools/AxiNSPack/Editors/AxibugNSPTools.cs b/Assets/AxiProjectTools/AxiNSPack/Editors/AxibugNSPTools.cs new file mode 100644 index 0000000..4da1036 --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack/Editors/AxibugNSPTools.cs @@ -0,0 +1,327 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEditor.Build.Reporting; +using UnityEngine; + +namespace AxibugEmuOnline.Editors +{ + public class AxibugNSPTools : Editor + { + static string WorkRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "AxiProjectTools/AxiNSPack")); + static string switch_keys = Path.GetFullPath(Path.Combine(Application.dataPath, "AxiProjectTools/AxiNSPack/switch_keys")); + static string hacpack_root = Path.GetFullPath(Path.Combine(Application.dataPath, "AxiProjectTools/AxiNSPack/hacpack")); + static Dictionary tools = new Dictionary(); + static string prodKeysPath; + + [MenuItem("Axibug移植工具/Switch/AxibugNSPTools/RepackNSP(仅重新构建NPS)")] + static void RepackNSP() + { + if (!CheckEnvironmentVariable()) + return; + + string path = EditorUtility.OpenFilePanel( + title: "选择 .nsp 文件", + directory: Path.Combine(Application.dataPath, ".."), // 默认路径为项目 Assets 目录 + extension: "nsp" // 限制文件类型为 .nsp + ); + + if (string.IsNullOrEmpty(path)) + return; + + RepackNSP(path); + } + + [MenuItem("Axibug移植工具/Switch/AxibugNSPTools/Build With RepackNSP(打包NSP并重新构建NPS)")] + static void BuildWithRepackNSP() + { + if (!CheckEnvironmentVariable()) + return; + + if (!EditorUtility.DisplayDialog("确认包信息", $"继续打包?", "继续", "取消")) + return; + + var levels = new List(); + foreach (var scene in EditorBuildSettings.scenes) + { + if (scene.enabled) + levels.Add(scene.path); + } + + var buildOpt = EditorUserBuildSettings.development ? BuildOptions.Development : BuildOptions.None; + if (EditorUserBuildSettings.buildWithDeepProfilingSupport) + buildOpt |= BuildOptions.EnableDeepProfilingSupport; + if (EditorUserBuildSettings.allowDebugging) + buildOpt |= BuildOptions.AllowDebugging; + + //勾选构建NSP包 + EditorUserBuildSettings.switchCreateRomFile = true; + +#if UNITY_2018_1_OR_NEWER && !UNITY_6000_0_OR_NEWER + string titleid = PlayerSettings.Switch.applicationID; +#else + string titleid = "null"; +#endif + string targetName = $"{Application.productName}_{titleid}.nsp"; + + string _locationPathName = $"Output/NSPBuild/{targetName}"; + var options = new BuildPlayerOptions + { + scenes = levels.ToArray(), + locationPathName = _locationPathName, + target = BuildTarget.Switch, + options = buildOpt + }; + + try + { + BuildReport report = BuildPipeline.BuildPlayer(options); + } + catch(Exception ex) + { + Debug.LogError($"[AxibugNSPTools] Unity Build NSP 错误:{ex.ToString()}"); + return; + } + + string NSPFullPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", _locationPathName)); + RepackNSP(NSPFullPath); + } + + static bool CheckEnvironmentVariable() + { + // 获取环境变量(需要添加环境变量检查) + string sdkRoot = Environment.GetEnvironmentVariable("NINTENDO_SDK_ROOT"); + if (string.IsNullOrEmpty(sdkRoot)) + { + Debug.LogError($"[AxibugNSPTools]请先正确配置环境变量:NINTENDO_SDK_ROOT,(若已配置,则保证配置后彻底重启Unity Hub和Unity)"); + return false; + } + + #region 获取prod.keys文件路径 + prodKeysPath = Path.Combine( + switch_keys, + "prod.keys" + ); + + if (!File.Exists(prodKeysPath)) + { + Debug.LogError($"[AxibugNSPTools]未找到 prod.keys! 请先准备文件,path:{prodKeysPath}"); + return false; + } + #endregion + + return true; + } + + static void RepackNSP(string nspFile) + { + #region 初始化工具路径 + // 获取环境变量(需要添加环境变量检查) + string sdkRoot = Environment.GetEnvironmentVariable("NINTENDO_SDK_ROOT"); + tools["authoringTool"] = Path.Combine(sdkRoot, "Tools/CommandLineTools/AuthoringTool/AuthoringTool.exe"); + tools["hacPack"] = Path.Combine(hacpack_root, "hacpack"); + #endregion + + #region 处理NSP文件路径 + string nspFilePath = nspFile; + string nspFileName = Path.GetFileName(nspFilePath); + string nspParentDir = Path.GetDirectoryName(nspFilePath); + #endregion + + #region 提取Title ID + string titleID = ExtractTitleID(nspFilePath); + if (string.IsNullOrEmpty(titleID)) + { + Debug.LogError($"[AxibugNSPTools]NSP文件名一部分,必须包含TitleID"); + return; + } + Debug.Log($"[AxibugNSPTools]Using Title ID: {titleID}"); + #endregion + + EditorUtility.DisplayProgressBar("AxibugNSPTools", $"清理临时目录", 0); + #region 清理临时目录 + CleanDirectory(Path.Combine(nspParentDir, "repacker_extract")); + CleanDirectory(Path.Combine(Path.GetTempPath(), "NCA")); + CleanDirectory(Path.Combine(WorkRoot, "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}\""); + + string controlPath = null; + string programPath = null; + FindNACPAndNPDPaths(extractPath, ref controlPath, ref programPath); + if (controlPath == null || programPath == null) + { + Debug.LogError("[AxibugNSPTools] Critical directory structure not found!"); + return; + } + #endregion + + #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", $"重建NSP", 0.9f); + string outputNSP = BuildFinalNSP(nspFilePath, nspParentDir, tmpPath, titleID); + EditorUtility.DisplayProgressBar("AxibugNSPTools", $"重建NSP", 1f); + Debug.Log($"[AxibugNSPTools]Repacking completed: {outputNSP}"); + EditorUtility.ClearProgressBar(); + #endregion + } + + #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)) + { + Directory.Delete(path, true); + while (Directory.Exists(path)) ; // 等待删除完成 + } + } + + static void FindNACPAndNPDPaths(string basePath, ref string controlPath, ref string programPath) + { + foreach (var dir in Directory.GetDirectories(basePath)) + { + if (File.Exists(Path.Combine(dir, "fs0/control.nacp"))) + controlPath = dir; + if (File.Exists(Path.Combine(dir, "fs0/main.npdm"))) + programPath = dir; + } + } + + static string ExecuteCommand(string command) + { + var process = new System.Diagnostics.Process() + { + StartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = $"/C {command}", + RedirectStandardOutput = true, + RedirectStandardError = true, // 增加错误流重定向 + UseShellExecute = false, + CreateNoWindow = true, + StandardOutputEncoding = Encoding.UTF8, // 明确指定编码 + StandardErrorEncoding = Encoding.UTF8 + } + }; + + var outputBuilder = new StringBuilder(); + var errorBuilder = new StringBuilder(); + + // 使用事件处理程序捕获实时输出 + process.OutputDataReceived += (sender, args) => + { + if (!string.IsNullOrEmpty(args.Data)) + { + outputBuilder.AppendLine(args.Data); + Debug.Log($"[AxibugNSPTools]{args.Data}"); + } + }; + + process.ErrorDataReceived += (sender, args) => + { + if (!string.IsNullOrEmpty(args.Data)) + { + errorBuilder.AppendLine(args.Data); + Debug.LogError($"[AxibugNSPTools]{args.Data}"); + } + }; + + process.Start(); + + // 开始异步读取输出 + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + // 等待进程退出(此时流已关闭) + process.WaitForExit(); + + // 将错误信息附加到主输出 + if (errorBuilder.Length > 0) + { + outputBuilder.AppendLine("\nError Output:"); + outputBuilder.Append(errorBuilder); + } + + return outputBuilder.ToString(); + } + #endregion + + #region NCA构建逻辑 + static string BuildProgramNCA(string tmpPath, string titleID, string programDir) + { + 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}"); + return ParseNCAOutput(output, "Program"); + } + + static string BuildControlNCA(string tmpPath, string titleID, string controlDir) + { + string args = $"-k \"{prodKeysPath}\" -o \"{tmpPath}\" --titleid {titleID} " + + $"--type nca --ncatype control --romfsdir \"{controlDir}/fs0\""; + + string output = ExecuteCommand($"{tools["hacPack"]} {args}"); + + return ParseNCAOutput(output, "Control"); + } + + static void BuildMetaNCA(string tmpPath, string titleID, string programNCA, string controlNCA) + { + string args = $"-k \"{prodKeysPath}\" -o \"{tmpPath}\" --titleid {titleID} " + + $"--type nca --ncatype meta --titletype application " + + $"--programnca \"{programNCA}\" --controlnca \"{controlNCA}\""; + + ExecuteCommand($"{tools["hacPack"]} {args}"); + } + + static string BuildFinalNSP(string origPath, string parentDir, string tmpPath, string titleID) + { + string outputPath = origPath.Replace(".nsp", "_repacked.nsp"); + if (File.Exists(outputPath)) File.Delete(outputPath); + + string args = $"-k \"{prodKeysPath}\" -o \"{parentDir}\" --titleid {titleID} " + + $"--type nsp --ncadir \"{tmpPath}\""; + + ExecuteCommand($"{tools["hacPack"]} {args}"); + File.Move(Path.Combine(parentDir, $"{titleID}.nsp"), outputPath); + return outputPath; + } + + static string ParseNCAOutput(string output, string type) + { + var line = output.Split('\n') + .FirstOrDefault(l => l.Contains($"Created {type} NCA:")); + return line?.Split(':').Last().Trim(); + } + #endregion + } +} diff --git a/Assets/AxiProjectTools/AxiNSPack/Editors/AxibugNSPTools.cs.meta b/Assets/AxiProjectTools/AxiNSPack/Editors/AxibugNSPTools.cs.meta new file mode 100644 index 0000000..a48f3e3 --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack/Editors/AxibugNSPTools.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 55aa3f0466c30bc4683cdbdc4dd75940 \ No newline at end of file diff --git a/Assets/AxiProjectTools/AxiNSPack/hacpack.meta b/Assets/AxiProjectTools/AxiNSPack/hacpack.meta new file mode 100644 index 0000000..f4c02cc --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack/hacpack.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d90c85ddb14ad7e4e9a6242ba135da0b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools.meta b/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools.meta new file mode 100644 index 0000000..e6fce36 --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b31e2ae7250c09548a777d4dcdfe2d1f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP.meta b/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP.meta new file mode 100644 index 0000000..708c152 --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7be57cd4293e9dc4297ea9b83fe08b18 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP/hptnacp.exe b/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP/hptnacp.exe new file mode 100644 index 0000000..7b48599 Binary files /dev/null and b/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP/hptnacp.exe differ diff --git a/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP/hptnacp.exe.meta b/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP/hptnacp.exe.meta new file mode 100644 index 0000000..81ccc3b --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP/hptnacp.exe.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e1252f6d74d67ee48af0a0342aecc981 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP/sample_hbemnu.nacp.xml b/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP/sample_hbemnu.nacp.xml new file mode 100644 index 0000000..09433f8 --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP/sample_hbemnu.nacp.xml @@ -0,0 +1,152 @@ +锘 + + + <Language>AmericanEnglish</Language> + <Name>Homebrew Menu</Name> + <Publisher>Yellows8</Publisher> + + + <Language>BritishEnglish</Language> + <Name>Homebrew Menu</Name> + <Publisher>Yellows8</Publisher> + + + <Language>Japanese</Language> + <Name>Homebrew Menu</Name> + <Publisher>Yellows8</Publisher> + + + <Language>French</Language> + <Name>Homebrew Menu</Name> + <Publisher>Yellows8</Publisher> + + + <Language>German</Language> + <Name>Homebrew Menu</Name> + <Publisher>Yellows8</Publisher> + + + <Language>LatinAmericanSpanish</Language> + <Name>Homebrew Menu</Name> + <Publisher>Yellows8</Publisher> + + + <Language>Spanish</Language> + <Name>Homebrew Menu</Name> + <Publisher>Yellows8</Publisher> + + + <Language>Italian</Language> + <Name>Homebrew Menu</Name> + <Publisher>Yellows8</Publisher> + + + <Language>Dutch</Language> + <Name>Homebrew Menu</Name> + <Publisher>Yellows8</Publisher> + + + <Language>CanadianFrench</Language> + <Name>Homebrew Menu</Name> + <Publisher>Yellows8</Publisher> + + + <Language>Portuguese</Language> + <Name>Homebrew Menu</Name> + <Publisher>Yellows8</Publisher> + + + <Language>Russian</Language> + <Name>Homebrew Menu</Name> + <Publisher>Yellows8</Publisher> + + + Required + Disable + None + AmericanEnglish + BritishEnglish + Japanese + French + German + LatinAmericanSpanish + Spanish + Italian + Dutch + CanadianFrench + Russian + Allow + Disable + 0x0104444444441001 + 2.0 + + CERO + 12 + + + ESRB + 10 + + + USK + 12 + + + PEGI + 12 + + + PEGIPortugal + 12 + + + PEGIBBFC + 12 + + + Russian + 12 + + + ACB + 13 + + + OFLC + 13 + + Required + All + 0x0104444444441001 + 0x0000000003e00000 + 0x0000000000180000 + 0x0000000000000000 + 0x0000000000000000 + 0x0000000000000000 + + 0x0104444444442001 + Nintendo + 0x0104444444441001 + Auto + 0x0000000000000000 + + AllOnLaunch + 0x0000000000000000 + 0x0000000000000000 + 0x0000000000000000 + 0x0000000000000000 + 0x0000000000000000 + 0x0000000000000000 + 0x0000000000000000 + 0x0000000000000000 + 0x0000000000000000 + None + Deny + Deny + 0x0000000000000000 + None + None + None + 0 + None + diff --git a/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP/sample_hbemnu.nacp.xml.meta b/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP/sample_hbemnu.nacp.xml.meta new file mode 100644 index 0000000..abd644c --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack/hacpack/Tools/hacPackTools-NACP/sample_hbemnu.nacp.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 42c1295c31de3a948825b9e8e9e8184f +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AxiProjectTools/AxiNSPack/hacpack/hacpack.exe b/Assets/AxiProjectTools/AxiNSPack/hacpack/hacpack.exe new file mode 100644 index 0000000..293ebb7 Binary files /dev/null and b/Assets/AxiProjectTools/AxiNSPack/hacpack/hacpack.exe differ diff --git a/Assets/AxiProjectTools/AxiNSPack/hacpack/hacpack.exe.meta b/Assets/AxiProjectTools/AxiNSPack/hacpack/hacpack.exe.meta new file mode 100644 index 0000000..d694a34 --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack/hacpack/hacpack.exe.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7b1b3ff7954facb409d3ba6f9840f762 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AxiProjectTools/AxiNSPack/switch_keys.meta b/Assets/AxiProjectTools/AxiNSPack/switch_keys.meta new file mode 100644 index 0000000..5ab90e7 --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack/switch_keys.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 409c6e8e5ead0ac4991ea6c243e407dd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AxiProjectTools/AxiNSPack/switch_keys/copy prod.keys To here.txt b/Assets/AxiProjectTools/AxiNSPack/switch_keys/copy prod.keys To here.txt new file mode 100644 index 0000000..e69de29 diff --git a/Assets/AxiProjectTools/AxiNSPack/switch_keys/copy prod.keys To here.txt.meta b/Assets/AxiProjectTools/AxiNSPack/switch_keys/copy prod.keys To here.txt.meta new file mode 100644 index 0000000..a36d51a --- /dev/null +++ b/Assets/AxiProjectTools/AxiNSPack/switch_keys/copy prod.keys To here.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 08bd0c8a53daacb4ea23b14dde156354 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md new file mode 100644 index 0000000..0cd14e8 --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +*本文仅作技术研究,因为合规原因,本文不提供任何法律敏感和版权相关的诸如SDK的文件。 + +### 1. Nintendo SDK + +> 这个我不会提供任何文件,或者建议您使用任天堂官方授权的开发套件(笑) + +准备好Nintendo SDK之后,配置如下环境变量: +NINTENDO_SDK_ROOT -> 你的路径\NintendoSDK +NINTENDO_SDK_NEX_ROOT -> 你的路径\NintendoSDK-NEX +NINTENDO_SDK_NPLN_ROOT -> 你的路径\NintendoSDK-NPLN +NINTENDO_SDK_PIA_ROOT -> 你的路径\NintendoSDK-Pia6 + +### 安装dotnet SDK + +> 因为Nintendo SDK依赖donetsdk的,在Nintendo Sdk中也直接包含dotnet sdk的安装文件(是doNet6).您也可以自行安装 + +这个建议一定要安装,缺少这个也是Unity打包异常的原因之一,否则你难以查找原因。 + +### Unity Switch Support + +> 同样,这个我不会提供任何文件,或者建议您使用任天堂官方授权的开发套件 + +准备和SDK对应版本的Unity Switch Support + +安装Unity-AAAA版本 +安装UnitySetup-Nintendo-Switch-Support-for-Editor-AAAA-SDK-BBBB-xxxxxxx.exe + +> 占位符: +> AAAA是Unity版本号 +> BBBB是Nintendo SDK版本号 + + +### 关于Unity License + +Nintendo Switch 并不需要特殊License或Unity Pro License,有一个License 就行 + +### Unity 开发过程 + +这个就和其他常见Unity游戏开发方式,忽略…… + + 有一些,平台差异的踩坑,类似正常的PC,IOS,Android的unity开发过程中一样,不同平台开发,有一些特定的平台兼容问题,或必须的差异做法。Switch也一样,有一些不同的兼容情况需要处理,后续踩坑,会详细讲述。但这个是游戏本身内容开发方面的,本文不在此赘述。 + +### 打包到Switch + +> 因为Unity是针对官方授权开发而来,NSP是提交给Nintendo,由Nintendo审核签名后分发,才会是一个完整带NCA头文件信息的可安装NSP。 + +> 并不是Android那样,输出.apk,就万事大吉。 + + 你大概率是没有Nintendo官方授权的开发套件的,按照官方套件,你打包出来的程序,只能在官方提供的开发机和测试机上运行,零售机是无法运行的。 + + 要安装到零售机,切是已经折腾过的零售机(Hacked) + 而且先决条件是,你提供的NSP是没有NCA签名等基本信息的,解决这个之后,才有安装的可能。 + + 不解决的话,通过MTP之类的安装方式,都会报错,无法安装: + > Invalid NCA Magic + +# 正片开始 + +在此之前,请你先确保你Unity开发环境,能够选择Switch平台,成功Build出文件。 +勾选 Create NSP File 是输入.nsp文件 +不勾选 则是输出的文件夹形式的你的程序文件 + +若Unity并不能选择Switch平台,请检查你的Switch Support是否准备好并安装, +若打包的时候并不能找到SDK,请检查你的Nintendo SDK是否准备好,并配置好环境变量。 +若IL2CPP问题,请按照Nintendo SDK解决。 +还有先解决基本问题,比如你的代码,在Build上不要报错,或者Switch的一些宏之类的。 + +能输出文件,好,现在才可以往下看。 + +# 打包使其让零售机(已折腾的)可以安装和运行 + +### 前置1.提取固件Key + +> 这是敏感操作,本文只是表示我自己的操作,对于操作的影响,本文不负任何责任。 + +你需要提取你SysNADA的key,这个固件密钥,是构建NCA等信息的关键。 +请不要在网络上寻找key,即便找到也是不可用的。 +请自行提取。 + +下载 lockpick_rcm.bin,复制到TF卡\bootloader\payloads +(如果你的折腾机已经整合,可以不下载) + +hekate界面,选择PayLoad(有些中文汉化了,叫有效载荷) +PayLoad中选择lockpick_rcm +进入lockpick_rcm之后,音量加减按钮是菜单上下选择,电源键是确定。 +我们选择 Dump from SysNADA,很快会将你的key保存到SDK上。 +退出Lockpick + +在SDK中会有这三个文件,将他拷贝到你的电脑上。 +sd:/switch/prod.keys +sd:/switch/title.keys +sd:/switch/dev.keys +> 你可以使用它,因为你合法拥有你的switch设备,但请仅自己提取使用,不要再网上分享或传播你的文件。避免一些法律风险,和避免您的设备敏感信息被泄露。 + +### 前置2.确定一个titleid + +自己定一个titleid,确保和现有所有已发行的游戏或者自制游戏的TitleID不一样, +可以去GBAtemp之类的论坛,查重。 + +并在Unity PlayerSetting中设置TitleID + +# 使用 AxibugNSPTools 重新打包NSP + + - 这工具由我提供,**工具内不提供也不包含任何Nintendo SDK版权相关的任何内容。**只是当你合法拥有Nintendo SDK时,本工具全自动化帮你调用。 + - **即本工具调用和操作的任何文件,都是您提供的合法文件,本工具不包含。给合法授权的开发者使用的。请遵守相关规定。** + +本工具自动化调用您提供的合法Nintendo SDK获取包的详细信息,并基于您提供的合法prod.keys,生成NAC,帮助您调用hacpack,最终repack一个新的NSP,使其可以在已经被折腾的Switch上,成功安装NSP文件。 + +- 拷贝keys + +AxibugNSPTools 是编写给Unity的工具插件,AxibugNSPTools,到您的项目目录. + +将您合法提取的prod.keys、title.keys、dev.keys拷贝到Assets/AxiProjectTools/AxiNSPack/switch_keys目录中。 +(注:您的项目若有提交git等公开仓库时,请忽略Assets/AxiProjectTools/AxiNSPack/switch_keys/*.keys , 以免泄露由您设备提取的key) + +*若没有prod.keys,程序repack时会提示您。 + +· 确保先前配置的Nintendo_SDK_ROOT环境变量 + +*若没有Nintendo_SDK_ROOT环境变量,程序repack时会提示您。 + +#### **功能1.** :RepackNSP(仅重新构建NPS) + +这个是把现有的Unity 输出的NSP(且无其他任何修改),重新构建,使其repack一个新的NSP,折腾后的零售Switch可以直接安装。 + +Unity Editor菜单上选择:Axibug移植工具/Switch/AxibugNSPTools/RepackNSP(仅重新构建NPS) + +等待执行完毕 + +通过MTP等方式安装到Switch测试 + +#### **功能2.** :Build With RepackNSP(打包NSP并重新构建NPS) + +这个是自动调用Unity Build一个NSP之后,再自动调用RepackNSP,重新构建,使其repack一个新的NSP,折腾后的零售Switch可以直接安装。 + +Unity Editor菜单上选择:Axibug移植工具/Switch/AxibugNSPTools/Build With RepackNSP(打包NSP并重新构建NPS) + +等待执行完毕 + +通过MTP等方式安装到Switch测试