news 2026/10/15 3:41:19

TWiLightMenu Slot-1 启动器源码解析:slot1launch 如何从 NitroHax 演变为实体卡带启动器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TWiLightMenu Slot-1 启动器源码解析:slot1launch 如何从 NitroHax 演变为实体卡带启动器
  • 嵌入式
  • 系统编程

【免费下载链接】TWiLightMenu

DSi Menu replacement for DS/DSi/3DS/2DS

项目地址:https://gitcode.com/gh_mirrors/tw/TWiLightMenu
点击查看免费下载

本篇技术指南以 slot1launch/README.md 为骨架,深入剖析 TWiLightMenu 项目中负责启动实体 NDS 卡带(Slot-1)的slot1launch模块:它脱胎于 Chishm 的 NitroHax 金手指工具,剥离金手指引擎后由 Apache Thunder 重建为 "Launch DS Cart" 的等价实现。通过阅读本文,你将掌握其 arm7/arm9 双核协作流程、bootloader 的加密卡带读取与启动机制、cardengine 的中断 Hook 与软复位原理,以及settings.ini与按游戏(per-game)配置的完整参数体系。

项目定位:NTR Launcher 的前世今生

slot1launch在 TWiLightMenu 中的角色是实体卡带启动器:当用户在 TWiLight Menu++ 中选择 "启动 Slot-1 卡带" 时,系统会将编译产物slot1launch.nds以slot1launch.srldr的形式加载执行(见 slot1launch/Makefile 中dist目标对../7zfile/_nds/TWiLightMenu/slot1launch.srldr的拷贝),由它接管并直接引导插入卡槽中的正版卡带或烧录卡。

README 中交代了它的历史脉络,这是理解整个模块的关键背景:

  • 源码基座是 NitroHax:NitroHax 是 Michael "Chishm" Chisholm 于 2008 年发布的 DS 金手指工具,slot1launch直接基于其源码改造,去掉了金手指引擎与菜单,只保留"启动器(Launcher)"一侧;
  • 重建动机:FileTrip 上曾经流传过一个名为 "Launch DS Cart" 的卡带启动程序,但其源码从未发布。在 ahezard 将 NitroHax 移植到最新 devkitARM 工具链之后,Apache Thunder 以此为蓝本重建了启动器;
  • GPL 授权:整个模块遵循 GNU GPL 许可(源码头部与 slot1launch/COPYING 一致),这也纠正了原 FileTrip 启动器"应附源码却未附"的历史问题。

从代码结构看,该模块由五个独立构建单元组成:slot1launch/arm7、slot1launch/arm9、slot1launch/bootloader、slot1launch/bootloaderAlt 与 slot1launch/cardengine_arm7,顶层 slot1launch/Makefile 的all目标依次构建cardengine_arm7 bootloader bootloaderAlt $(TARGET).nds。

双核协作:arm9 主流程与 arm7 卡槽控制

arm9:读取配置并准备启动

slot1launch/arm9/source/main.cpp 是启动器的入口(main())。它的核心职责按顺序是:

  1. 禁用系统睡眠:fifoSendValue32(FIFO_PM, PM_REQ_SLEEP_DISABLE);
  2. 检查卡槽供电状态:在 DSi 模式下读取REG_SCFG_MC,若为0x11/0x10(卡槽断电状态)则通过FIFO_USER_02通知 arm7 需要上电;
  3. 初始化 FAT 并读取配置文件:fatInitDefault()成功后读取/_nds/TWiLightMenu/settings.ini与按游戏生成的/_nds/TWiLightMenu/gamesettings/slot1/{游戏TID}.ini;
  4. 与 arm7 握手完成卡带复位:发送FIFO_USER_01通知 arm7 准备就绪,fifoWaitValue32(FIFO_USER_03)等待复位完成,随后等待 30 个 VBlank 让卡带信号稳定;
  5. 抢占卡带总线并读取头信息:sysSetCardOwner(BUS_OWNER_ARM9)后调用cardReadHeader()读取 512 字节的 NDS 头(含游戏代码 gameCode);
  6. 非 DSi 模式下的容错处理:若读取到的头全为0xffffffff(未插卡),则初始化演示控制台并提示 "Please insert a DS game" 或 "Please insert a DS game or flashcard"(取决于 DLDI 特性是否支持 GBA 槽),并轮询等待插卡;同时用swiCRC16校验头 CRC,失败则提示 "Please reboot the console with Slot-1 empty, and try again."。

底层卡带头读取由 slot1launch/arm9/source/nds_card.c 的getHeader()完成:先发送CARD_CMD_DUMMY哑命令,再以CARD_ACTIVATE | CARD_nRESET | CARD_CLK_SLOW参数执行CARD_CMD_HEADER_READ读取 512 字节。

arm7:卡槽上电/断电与复位

slot1launch/arm7/source/main.c 在初始化 FIFO 与 IRQ 后,若处于 DSi 模式则执行关键握手:

fifoWaitValue32(FIFO_USER_01); // 等 arm9 就绪 if (fifoCheckValue32(FIFO_USER_02)) { // arm9 报告卡槽断电 if (fifoCheckValue32(FIFO_USER_07)) { TWL_ResetSlot1(); } else { PowerOnSlot(); } } fifoSendValue32(FIFO_USER_03, 1); // 通知 arm9 复位完成

卡槽电源状态机实现在 slot1launch/arm7/source/resetslot.c:通过写REG_SCFG_MC依次推进状态(0x04→ 等待、0x08→ 等待)完成上电,0x0C完成断电,TWL_ResetSlot1()则先断电 30 帧再上电,以实现硬复位。该文件还通过 I2C 写寄存器(0x4A器件上的0x12/0x70)设置自动复位与跳过健康安全提示的启动标志。

配置体系:全局设置与按游戏覆盖

slot1launch的配置全部来自 arm9 的CIniFile解析,分为两层,均可直接编辑 SD 卡上的 ini 文件生效。

全局设置/_nds/TWiLightMenu/settings.ini

arm9 主流程读取的键值(节名SRLOADER与NDS-BOOTSTRAP)如下:

节键默认值作用
SRLOADERIGNORE_BLACKLISTS0置 1 时绕过 CPU 超频黑名单(见下文setClockSpeed)
SRLOADERSLEEP_MODE1是否保留游戏的睡眠模式功能
SRLOADERSLOT1_CARDENGINE1是否注入 cardengine(中断 Hook、软复位、金手指数据传递)
SRLOADERSLOT1_ENABLESD0启动后是否允许 SD 卡访问(影响 SCFG 配置)
SRLOADERSLOT1_SCFG_UNLOCK0是否解锁 SCFG 寄存器以允许更激进的内存/时钟配置
SRLOADERRESET_SLOT11启动前是否执行一次卡槽硬复位
NDS-BOOTSTRAPSOUND_FREQ00 为 32kHz,1 为 48kHz 采样率
NDS-BOOTSTRAPLANGUAGE-1强制游戏语言(0~7 对应固件语言编号,-1 使用默认)

这些键值在 slot1launch/arm9/source/main.cpp 中被逐一读取,其中runCardEngine还支持运行时切换:scanKeys()后若按住 A 键启动,则runCardEngine = !runCardEngine。

按游戏设置/_nds/TWiLightMenu/gamesettings/slot1/{TID}.ini

arm9 用卡带头的 4 字节游戏代码gameTid拼出pergamefilepath后读取(slot1launch/arm9/source/main.cpp),键位于GAMESETTINGS节:

键默认值作用
DSI_MODEDEFAULT_DSI_MODE(1)是否以 DSi 增强模式启动(-1 用默认值,0/1 显式指定)
BOOST_CPUDEFAULT_BOOST_CPU(false)是否将 CPU 提升至 TWL 频率
BOOST_VRAMDEFAULT_BOOST_VRAM(false)是否启用扩展 VRAM 访问

默认常量统一定义在 universal/include/defaultSettings.h。其中 CPU 超频还受黑名单约束:setClockSpeed()会遍历twlClockExcludeList,若游戏 TID 前 3 字节命中黑名单则强制返回false(除非IGNORE_BLACKLISTS已开启),这部分逻辑位于 slot1launch/arm9/source/main.cpp。

参数如何到达 bootloader

arm9 读取的上述所有参数并不会直接传给游戏,而是通过 slot1launch/arm9/source/launch_engine.c 写入 VRAM-D 中 bootloader 二进制的固定偏移处(DSIMODE_OFFSET、LANGUAGE_OFFSET、SDACCESS_OFFSET、SCFGUNLOCK_OFFSET、TWLMODE_OFFSET、TWLCLOCK_OFFSET、BOOSTVRAM_OFFSET、TWLTOUCH_OFFSET、SOUNDFREQ_OFFSET、SLEEPMODE_OFFSET、RUNCARDENGINE_OFFSET,偏移依次为 4、8、12…44),再由 bootloader 的 arm7 侧extern u32 language/sdAccess/scfgUnlock/...读取。这种"参数放在执行代码头部的固定偏移"方案是 NitroHax 时代就确立的轻量 IPC 模式。

启动引擎:从 PassMe 循环到软复位交接

slot1launch/arm9/source/launch_engine.c 的runLaunchEngine()是整个交接过程的核心,值得逐段拆解:

// 1. 关闭所有中断,使 VRAM-D 进入 CPU 直写模式并清零 irqDisable(IRQ_ALL); VRAM_D_CR = VRAM_ENABLE | VRAM_D_LCD; toncset(LCDC_BANK_D, 0, 128 * 1024); // 2. 把 bootloader 拷入 VRAM-D(0x06860000) tonccpy(LCDC_BANK_D, altBootloader ? loadAlt_bin : load_bin, altBootloader ? loadAlt_bin_size : load_bin_size); // 3. 写入参数区(见上文) toncset32((u8*)LCDC_BANK_D + DSIMODE_OFFSET, isDSiMode(), 1); // ... LANGUAGE / SDACCESS / SCFGUNLOCK / TWLMODE / TWLCLOCK / BOOSTVRAM / // TWLTOUCH / SOUNDFREQ / SLEEPMODE / RUNCARDENGINE 同理 // 4. 可选注入 cardengine(仅 NTR 模式) *(u32*)0x02800000 = cardengine_arm7_bin_size; tonccpy((u32*)0x02800004, cardengine_arm7_bin, cardengine_arm7_bin_size); // 5. 把 VRAM-D 交给 arm7,卡带总线交给 arm7 VRAM_D_CR = VRAM_ENABLE | VRAM_D_ARM7_0x06020000; REG_EXMEMCNT |= ARM7_OWNS_ROM | ARM7_OWNS_CARD; // 6. 构造 PassMe 循环并复位双核 *(vu32*)0x02FFFE04 = (u32)0xE59FF018; // ldr pc, 0x02FFFE24 *(vu32*)0x02FFFE24 = (u32)0x02FFFE04; resetARM7(0x06020000); swiSoftReset();

altBootloader参数的取值来自 slot1launch/arm9/source/main.cpp:当卡带游戏代码命中UBRP(DS Browser)、AMFE、ALXX或游戏标题含D!S!XTREME时使用loadAlt_bin(即 bootloaderAlt 产物),其中UBRP还会以isDSBrowser=true传入;main.cpp同时在 DSi 模式下检测到 SDK5 卡带(unitCode > 0 && TWLMODE)时强制关闭 cardengine,避免与原生 DSi 模式冲突。

bootloader 深度:加密卡带读取与内存布置

bootloader 是真正"读懂"卡带的部件,位于 slot1launch/bootloader/source,其中 arm9 侧(main.arm9.c)负责清缓存、清内存、配置 MBK/SCFG 后跳转到游戏的 ARM9 入口,arm7 侧(main.arm7.c)负责卡带初始化、二进制加载、打补丁后跳转。

卡带安全区解密:KEY1/KEY2 与 Blowfish

slot1launch/bootloader/source/read_card.c 完整实现了商业卡带的加密读取协议:

  • cardInit():发送哑命令、读取 chip ID(其最高位决定安全区块大小,normalChip为 0x200 字节/块,否则 0x1000 字节/块)、读取 0x1000 字节扩展头并校验headerCRC16(失败返回ERR_HEAD_CRC);
  • KEY1 加密:initKey1Encryption()用"公平骰子掷出的随机数"(getRandomNumber()固定返回 4,源码注释 "chosen by fair dice roll. guaranteed to be random.")生成命令参数,配合 Blowfish 初始化后发送CARD_CMD_ACTIVATE_BF(NTR)或CARD_CMD_ACTIVATE_BF2(DSi);
  • KEY2 加密:createEncryptedCommand()对命令逐字节加密并回写key1data.kkkkk递增计数器,随后配置REG_CARD_1B0/1B4/1B8/1BA密钥寄存器;
  • 安全区读取与解密:通过CARD_CMD_SECURE_READ分块拉取安全区,decryptSecureArea()用crypt_64bit_down解密;若安全区标记为'encr'/'yObj'则清除标记,否则整块填充0xe7ffdeff——源码注释指出该处理"曾成功引导 DS-Xtreme,可能大幅提升烧录卡兼容性";
  • DSi 专属:switchToTwlBlowfish()针对 DSiWare/双 ROM 卡带切换 Blowfish 密钥表(DSI_CARD_KEY/DSI_DEV_CARD_KEY),并处理twl_rom_region_start指定的第二安全区。

加载与修补流程

arm7_main()(slot1launch/bootloader/source/main.arm7.c)按固定剧本推进:

  1. arm7_resetMemory()清零全部可见内存(特意保留0x023F0000处的金手指数据区和0x02400000的 TWLCFG 备份);
  2. arm7_loadBinary()调用cardInit()后按头部偏移把 ARM9/ARM7 二进制经cardRead()读入目标地址,并借moduleParamsSignature(0xDEC00621, 0x2106C0DE)在 ARM9 二进制中搜索 module params;
  3. my_readUserSettings()从固件读回用户设置(昵称、语言、出生日期等),支持按 CRC 选择固件双槽中较新的一份,并写回语言覆盖;对 iQue 中文版游戏还会打"免区域"补丁并重算头 CRC;
  4. 依twlMode判定dsiModeConfirmed,加载并解密arm9i/arm7i(DSi 增强二进制,FULL_DSI_MODE_ENABLED宏控制,见 slot1launch/bootloader/source/common.h);
  5. 依据游戏代码做特判:Pokémon 系列(ADA/APA/CPU/IPK/IPG)修补 header 数据;DS Browser(UBRP)执行fixDSBrowser()的 Opera RAM 补丁(slot1launch/bootloader/source/main.arm7.c);NTR/ASM(Super Mario 64 DS)/AMC(Mario Kart DS)/A2D(New Super Mario Bros.)等 8 组游戏代码启用游戏内软复位标志;
  6. 可选的patchSleepInputWrite():当SLEEP_MODE=0时,在 arm7 二进制中按特征签名(0x13A04902或 Thumb0xD000)定位睡眠输入写入指令并替换为nop,从而禁用合盖睡眠;
  7. setMemoryAddress()在0x027FF800(或 SDK5 的0x02FFF800)及副本地址写入chipID、头 CRC、安全区 CRC 与0x5835等卡带信息,并设置0x1的 Boot Indicator——源码注释强调该位 "EXTREMELY IMPORTANT",对 SDK5 二进制必须为 1(致谢 cReDiAr);
  8. 最后经ARM9_SETSCFG状态机让 arm9 侧完成 SCFG/MBK 配置,双核在 VCOUNT=191 的帧边界同步后分别arm9_actual_jump/arm7_actual_jump跳入游戏入口。

arm9 侧(slot1launch/bootloader/source/main.arm9.c)除清 ITCM/DTCM、清 DMA 与视频寄存器外,还实现了arm9_errorOutput()诊断屏:出错时按"每 2 位一个色块"的编码把错误码绘制为红/黄/绿/蓝方块,错误码定义见 slot1launch/bootloader/source/common.h(如ERR_HEAD_CRC 0x16、ERR_HOOK 0x22),方便无屏输出环境下定位问题。

bootloaderAlt:面向特殊卡带的替代引导

slot1launch/bootloaderAlt 是与主 bootloader 并行的第二套引导实现,二者编译目标分别为load与loadAlt(见两份 Makefile)。从结构对比看:

  • 架构更保守:主 bootloader 以-march=armv5te编译并包含 DSi 全功能代码(gm9i、polarssl、twltool加密组件、scfg_clock_change.arm9.s、key2.h/key2_dev.h等,见 slot1launch/bootloader/Makefile),而 bootloaderAlt 仅用-march=armv4t、内嵌cardengine_arm7.bin(BINFILES := cardengine_arm7.bin),代码规模显著更小;
  • 固化 NDS 头地址:main.arm7.c直接以NDS_HEAD 0x027FFE00作为头指针,并通过自实现的 SPI 固件读取(arm7_readFirmware(),slot1launch/bootloaderAlt/source/main.arm7.c)替代 libnds 的readFirmware,减少依赖;
  • 用途:从 slot1launch/arm9/source/launch_engine.c 看,它专门服务 DS Browser、部分不支持常规引导的卡带,以及对REG_SCFG_EXT做差异化的 SCFG 配置场景(isDSBrowser || scfgUnlock时写0x8300E000/0x8300C000,否则写0x83002000/0x83000000)。

cardengine:VBlank Hook、软复位与返回菜单

slot1launch/cardengine_arm7/source/cardEngine.c 是注入到游戏 arm7 的常驻代码,安装机制在 slot1launch/bootloader/source/hook.c:hookNdsRetail()按特征签名在游戏 arm7 二进制中定位 IRQ 中断处理函数(handlerStartSig的 5 条指令push {lr} / mov ip,#0x4000000 / ...与handlerEndSig的 4 条指令),找到其 VBlank 处理入口后,把原入口保存在cardEngineLocation[1],并把 cardengine 自己的入口写回向量表。对 SDK5(DSi 增强)游戏,则按 arm7 二进制大小查表填入已知固定地址(如0x239D280对应 DS WiFi Settings 等 20 余个大小条目)。

cardengine 的 VBlank 处理器myIrqHandlerVBlank()承担三件事:

  1. 语言注入:language在 0~7 范围时持续写入0x027FFCE4;
  2. 组合键软复位:同时按住L+R+↓+B达 2 秒(softResetTimer == 60*2)时,经unlaunchSetFilename()写入 Unlaunch 的AutoLoadInfo(CRC16 固定为0x3F0长度),将自动加载路径改写为sdmc:/boot.nds后rebootConsole()重启进入 TWiLight Menu++;同时按住L+R+START+SELECT则改写为sdmc:/_nds/TWiLightMenu/main.srldr直接复位游戏(gameSoftReset标志会禁用该组合);
  3. DSi RTC 修复:清除IRQ_NETWORK标志后调用cheat_engine_start(),金手指数据(宽屏金手指wideCheatData.bin或0x023F0000处由 arm9 预置的 0x8000 字节数据)经此传入游戏。arm9 侧在 slot1launch/arm9/source/main.cpp 会读取sd:/_nds/nds-bootstrap/wideCheatData.bin并以其长度标记0xCF签名后拷入0x023F0000。

构建与产出

slot1launch采用 devkitARM 工具链构建,顶层 slot1launch/Makefile 要求环境变量DEVKITARM指向 devkitARM 安装路径(export DEVKITARM=<path to>devkitARM),随后:

make # 依次构建 cardengine_arm7、bootloader、bootloaderAlt 与 slot1launch.nds make dist # 将产物拷贝为 7zfile/_nds/TWiLightMenu/slot1launch.srldr 及 arm7/arm9 ELF 调试文件

其中slot1launch.nds通过ndstool合成:

ndstool -c slot1launch.nds -7 slot1launch.arm7.elf -9 slot1launch.arm9.elf \ -b icon.bmp "Slot-1 Launcher;TWiLight Menu++;Apache Thunder & Rocket Robz" \ -g TWL1 01 "TWLMENUPP-S1" -z 80040000 -u 00030004

版本号定义于VERSION_MAJOR 1/VERSION_MINOR 99(即 1.99)。arm9 与两个 bootloader 均复用仓库根目录下的universal公共库(如tonccpy、defaultSettings.h、ds_arm9_twlm.specs链接脚本),arm9 以-std=gnu++17编译并链接-lfat -lnds9,bootloader 则以裸机链接脚本load.ld构建。仓库同时提供了clean and compile.bat、compile.bat(Windows)与compile_docker.ps1(Docker)等便捷入口。

小结

从 NitroHax 剥离金手指引擎、经 ahezard 的 devkitARM 移植、再由 Apache Thunder 重建,slot1launch至今承担着 TWiLight Menu++ 中实体卡带启动的最后一公里:arm9 读配置、arm7 管卡槽、bootloader 解密并布置内存、cardengine 常驻注入提供软复位与宽屏金手指通道。理解这套分层交接(settings.ini→ VRAM-D 参数区 → bootloader 状态机 → 游戏入口),也就理解了整个 NTR 卡带引导生态的地基——它既是 TWiLightMenu 的组成部分,也是 NitroHax 技术遗产在 GPL 许可下的延续。

  • 嵌入式
  • 系统编程

【免费下载链接】TWiLightMenu

DSi Menu replacement for DS/DSi/3DS/2DS

项目地址:https://gitcode.com/gh_mirrors/tw/TWiLightMenu
点击查看免费下载

相关推荐

上一篇:fhEVM 前端工程化:@zama-fhe/relayer-sdk 常见 Webpack 打包错误排查与解决指南
下一篇:Anchor(Solana 程序框架)开发指南:Rust eDSL、IDL 规范、TypeScript 客户端与 AVM 工作流

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 3:40:17

我写了个爬虫-1000条数据5分钟抓完还不会崩

我写了个爬虫&#xff0c;1000 条数据 5 分钟抓完&#xff0c;还不会崩 需要抓取 books.toscrape.com 上 1000 本书的书名、价格、评分&#xff0c;分页共 50 页。脚本一次运行 5 分钟左右抓完&#xff0c;全程零人工干预&#xff1b;最终实收 1000 条&#xff0c;缺失 0、重复…

作者头像 李华
网站建设 2026/10/15 3:39:55

大麦网自动抢票指南:如何用 Python 脚本 4 步跑通自动购票流程

大麦网自动抢票指南&#xff1a;如何用 Python 脚本 4 步跑通自动购票流程 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase 19:30:00&#xff0c;大麦网开票倒计时归零的那一刻…

作者头像 李华
网站建设 2026/10/15 3:39:06

时序网络瓦解Ⅲ--时序供应链网络中的级联失效和恢复机制

一、概念背景 三种经典级联失效模型。级联失效是指局部节点 / 边的失效会通过负载重分配、邻接影响等方式连锁传导,最终引发全网大范围崩溃的过程,是网络鲁棒性、网络瓦解研究的重要基础方向。 一、容量 - 负载模型(Motter-Lai 模型,2002) 核心原理 这是最经典的负载…

作者头像 李华
网站建设 2026/10/15 3:38:51

华为ENSP实战:ACL访问控制列表从原理到配置排错

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华