
Add config option to autoboot into 3DS and DSi homebrew menu, without going through Home Menu (nor launching it). For 3DS homebrew, this requires homebrew built with libctru v2.0.0 or later (v2.0.0 was released 2.5y ago). We simulate a "reboot into title" to achieve this. This being said, when launching stuff like Pokemon US/UM on O3DS, Home Menu reboots into itself and not the game directly. This will cause Home Menu to crash if you use this feature and configure it to use a non-default memory layout (but if you don't, Home Menu will work just fine).
210 lines
6.8 KiB
C
210 lines
6.8 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* 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"
|
|
|
|
u8 *loadDeliverArg(void)
|
|
{
|
|
static 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 crc = *(u32 *)(deliverArg + 0x43C);
|
|
u32 expectedCrc = crc32(deliverArg + 0x400, 0x140, 0xFFFFFFFF);
|
|
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 || crcLen <= 248 || 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) = 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;
|
|
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);
|
|
|
|
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);
|
|
|
|
CFG_BOOTENV = 3;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool configureHomebrewAutoboot(void)
|
|
{
|
|
bool ret;
|
|
u8 *deliverArg = loadDeliverArg();
|
|
|
|
u32 bootenv = CFG_BOOTENV;
|
|
u32 mode = bootenv >> 1;
|
|
|
|
u32 testPattern = *(u32 *)(deliverArg + 0x438);
|
|
if (mode != 0 || testPattern == 0xFFFF)
|
|
return false; // bail out if this isn't a coldboot/plain reboot
|
|
|
|
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;
|
|
}
|