/* * This file is part of Luma3DS * Copyright (C) 2022 Aurora Wright, TuxSH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional Terms 7.b and 7.c of GPLv3 apply to this file: * * Requiring preservation of specified reasonable legal notices or * author attributions in that material or in the Appropriate Legal * Notices displayed by works containing it. * * Prohibiting misrepresentation of the origin of that material, * or requiring that modified versions of such material be marked in * reasonable ways as different from the original version. */ #include "deliver_arg.h" #include "utils.h" #include "memory.h" #include "config.h" #include "fs.h" #include "i2c.h" #include "screen.h" u8 *loadDeliverArg(void) { static __attribute__((aligned(8))) u8 deliverArg[0x1000] = {0}; static bool deliverArgLoaded = false; if (!deliverArgLoaded) { u32 bootenv = CFG_BOOTENV; // this register is preserved across reboots if ((bootenv & 1) == 0) // true coldboot { memset(deliverArg, 0, 0x1000); } else { u32 mode = bootenv >> 1; if (mode == 0) // CTR mode { memcpy(deliverArg, (const void *)0x20000000, 0x1000); // Validate deliver arg u32 testPattern = *(u32 *)(deliverArg + 0x438); u32 *crcPtr = (u32 *)(deliverArg + 0x43C); u32 crc = *crcPtr; *crcPtr = 0; // clear crc field before calculation u32 expectedCrc = crc32(deliverArg + 0x400, 0x140, 0xFFFFFFFF); *crcPtr = crc; if (testPattern != 0xFFFF || crc != expectedCrc) memset(deliverArg, 0, 0x1000); } else // Legacy modes { // Copy TWL deliver arg stuff as-is (0...0x300) copyFromLegacyModeFcram(deliverArg, (const void *)0x20000000, 0x400); // Validate TLNC (TWL launcher params) block // Note: Nintendo doesn't do crcLen bound check u8 *tlnc = deliverArg + 0x300; bool hasMagic = memcmp(tlnc, "TLNC", 4) == 0; u8 crcLen = tlnc[5]; u16 crc = *(u16 *)(tlnc + 6); if (!hasMagic || (8 + crcLen) > 0x100 || crc != crc16(tlnc + 8, crcLen, 0xFFFF)) memset(tlnc, 0, 0x100); memset(deliverArg + 0x400, 0, 0xC00); } } deliverArgLoaded = true; } return deliverArg; } void commitDeliverArg(void) { u8 *deliverArg = loadDeliverArg(); u32 bootenv = CFG_BOOTENV; if ((bootenv & 1) == 0) // if true coldboot, set bootenv to "CTR mode reboot" { bootenv = 1; CFG_BOOTENV = 1; } u32 mode = bootenv >> 1; if (mode == 0) // CTR mode { *(u32 *)(deliverArg + 0x438) = 0xFFFF; *(u32 *)(deliverArg + 0x43C) = 0; // clear CRC field before calculating it *(u32 *)(deliverArg + 0x43C) = crc32(deliverArg + 0x400, 0x140, 0xFFFFFFFF); memcpy((void *)0x20000000, deliverArg, 0x1000); } else // Legacy modes (just TWL mode, really) { copyToLegacyModeFcram((void *)0x20000000, deliverArg, 0x400); } } bool hasValidTlncAutobootParams(void) { u8 *tlnc = loadDeliverArg() + 0x300; // loadDeliverArg clears invalid TLNC blocks return memcmp(tlnc, "TLNC", 4) == 0 && (*(u16 *)(tlnc + 0x18) & 1) != 0; } bool isTwlToCtrLaunch(void) { // assumes TLNC block is valid u8 *tlnc = loadDeliverArg() + 0x300; // loadDeliverArg clears invalid TLNC blocks u64 twlTid = *(u64 *)(tlnc + 0x10); switch (twlTid & ~0xFFull) { case 0x0000000000000000ull: // TWL Launcher -> Home menu (note: NS checks full TID) case 0x00030015484E4200ull: // TWL System Settings -> CTR System Settings (mset) return true; default: return false; } } static bool configureHomebrewAutobootCtr(u8 *deliverArg) { static const u8 appmemtypesO3ds[] = { 0, 2, 3, 4, 5 }; static const u8 appmemtypesN3ds[] = { 6, 7, 7, 7, 7 }; u64 hbldrTid = configData.hbldr3dsxTitleId; hbldrTid = hbldrTid == 0 ? HBLDR_DEFAULT_3DSX_TID : hbldrTid; // replicate Loader's behavior if ((hbldrTid >> 46) != 0x10) // Not a CTR titleId. Bail out return false; u8 memtype = configData.autobootCtrAppmemtype; // autobootCtrAppmemtype already checked, but it doesn't hurt to check again memtype = memtype >= 5 ? 0 : memtype; deliverArg[0x400] = ISN3DS ? appmemtypesN3ds[memtype] : appmemtypesO3ds[memtype]; // Determine whether to load from the SD card or from NAND. We don't support gamecards for this u32 category = (hbldrTid >> 32) & 0xFFFF; bool isSdApp = (category & 0x10) == 0 && category != 1; // not a system app nor a DLP child *(u64 *)(deliverArg + 0x440) = hbldrTid; *(u64 *)(deliverArg + 0x448) = isSdApp ? 1 : 0; // Tell NS to run the title, and that it's not a title jump from legacy mode *(u32 *)(deliverArg + 0x460) = (0 << 1) | (1 << 0); // Whenever power button is held long enough ("force shutdown"), mcu sysmodule // stores a flag in free reg 0. It will clear it next boot. // During that next boot, if that flag was set and if CFG_BOOTENV.bit0 is set // (warmboot/firm chainload, i.e. not coldbooting), then main() will simulate // a "power button held" interrupt (after upgrading mcu fw if necessary -- it // will reboot console after if it has upgraded mcu fw, I guess that's one of // the reasons the flag is there). This obviously cause other processes to initiate // a shutdown, and it also sets that flag again. // In the case of autoboot, ns will panic when this happens. This caused // hb autoboot to keep failing over and over again. // Select free reg 0, read it, select it again, write it (clearing force shutdown flag) I2C_writeReg(I2C_DEV_MCU, 0x60, 0); u8 flags = I2C_readReg(I2C_DEV_MCU, 0x61); flags &= ~4; I2C_writeReg(I2C_DEV_MCU, 0x60, 0); I2C_writeReg(I2C_DEV_MCU, 0x61, flags); CFG_BOOTENV = 1; return true; } static bool configureHomebrewAutobootTwl(u8 *deliverArg) { // Here, we pretend to be a TWL app rebooting into another TWL app. // We get NS to do all the heavy lifting (starting NWM and AM, etc.) this way. memset(deliverArg + 0x000, 0, 0x300); // zero TWL deliver arg params // Now onto TLNC (launcher params): u8 *tlnc = deliverArg + 0x300; memset(tlnc, 0, 0x100); memcpy(tlnc, "TLNC", 4); tlnc[4] = 1; // version tlnc[5] = 0x18; // length of data to calculate CRC over *(u64 *)(tlnc + 8) = 0; // old title ID *(u64 *)(tlnc + 0x10) = configData.autobootTwlTitleId; // new title ID // bit4: "skip logo" ; bits2:1: NAND boot ; bit0: valid *(u16 *)(tlnc + 0x18) = (1 << 4) | (3 << 1) | (1 << 0); *(u16 *)(tlnc + 6) = crc16(tlnc + 8, 0x18, 0xFFFF); // Even though (when running TWL/AGB FIRM) the SoC is in O3DS mode, and the GPU also is, // as well as most other components behaving as such (external RAM, L2C not usable, etc.), // this is NOT the case for the LCD and adaptive backlight logic which retains FULL N3DS // functionality, including a feature where the window is blended with a given color depending // on the overall relative luminance of that window. // However, Nintendo's own code mistakenly assumes the opposite, and clearly so ("if GPU in N3DS mode" // checks, not passing N3DS extra adaptive backlight (ABL) to TWL/AGB_FIRM). This has implications: // - Powersaving (ABL) settings in TWL/AGB_FIRM is inconsistent with *both* O3DS (because the new RGB blend LUT // has been set to its current value by NATIVE_FIRM) and N3DS (because "pwn_cnt" and "inertia" are missing // their N3DS-only bits) // - "rave party" when booting into TWL/AGB_FIRM or O3DS NATIVE_FIRM without these regs (well, the LUT) initialized. // Easiest way to do so is by leveraging the "DSi autooboot" feature Luma provides. It is worth noting at least // the LUT survives hardware reboots (if Nintendo were using DSi software that was using TLNC-based reboots, // they wouldn't have noticed). // As such, zerofill these registers (from testing, hardware explicitly discards null values, so this // should be fine). For now, only touch the Luma-initiated autoboot path if (ISN3DS) zerofillN3dsAblRegisters(); CFG_BOOTENV = 3; return true; } bool configureHomebrewAutoboot(void) { bool ret; u8 *deliverArg = loadDeliverArg(); u32 bootenv = CFG_BOOTENV; u32 mode = bootenv >> 1; // NS always writes a valid deliver arg on reboot, no matter what. // Check if it is empty, and, of course, bail out if we aren't rebooting from // NATIVE_FIRM. // Checking if it is empty is necessary to let us reboot from autobooted hbmenu // to hbmenu. if (mode != 0) return false; else if (bootenv == 1) { for (u32 i = 0; i < 0x410; i++) { if (deliverArg[i] != 0) return false; } for (u32 i = 0x440; i < 0x1000; i++) { if (deliverArg[i] != 0) return false; } } switch (MULTICONFIG(AUTOBOOTMODE)) { case 1: ret = configureHomebrewAutobootCtr(deliverArg); break; case 2: ret = configureHomebrewAutobootTwl(deliverArg); break; case 0: default: ret = false; break; } if (ret) commitDeliverArg(); return ret; }