Rework install/unlinstall flow

Restructure the code and use the same approach as the launcher tmd restorer to deal with tmds.
Store all the known tmds for all the launcher versions with their corresponding shas, and use them when restoring the console.
Now if the main tmd isn't either the stock one or one with the patch done by this program, it's treated a invalid and will be restored from the saved ones when uninstalling.
This breaks for now proto/dev consoles as there is no handling for "default tmds" on them and the program logic has to be changed accordingly
This commit is contained in:
Edoardo Lolletti 2025-08-12 17:42:03 +02:00
parent f7fb51c244
commit cae02f6607
131 changed files with 932 additions and 699 deletions

View File

@ -53,6 +53,7 @@ $(TARGET).dsi : $(NITRO_FILES) arm7/$(TARGET).elf arm9/$(TARGET).elf
-g "$(GAME_CODE)" "00" "$(GAME_LABEL)" \ -g "$(GAME_CODE)" "00" "$(GAME_LABEL)" \
-b $(GAME_ICON) "$(GAME_TITLE);$(GAME_SUBTITLE1)" \ -b $(GAME_ICON) "$(GAME_TITLE);$(GAME_SUBTITLE1)" \
$(_ADDFILES) $(_ADDFILES)
cp $(TARGET).dsi ntrboot.nds
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
arm7/$(TARGET).elf: arm7/$(TARGET).elf:
@ -66,4 +67,4 @@ arm9/$(TARGET).elf:
clean: clean:
$(MAKE) -C arm9 clean $(MAKE) -C arm9 clean
$(MAKE) -C arm7 clean $(MAKE) -C arm7 clean
rm -f $(TARGET).dsi $(TARGET).arm7 $(TARGET).arm9 rm -f $(TARGET).dsi $(TARGET).arm7 $(TARGET).arm9 ntrboot.nds

View File

@ -31,7 +31,7 @@ CFLAGS := -g -Wall -O2\
$(ARCH) $(ARCH)
CFLAGS += $(INCLUDE) -DARM9 CFLAGS += $(INCLUDE) -DARM9
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++23 CXXFLAGS := $(CFLAGS) -fno-rtti -fexceptions -std=gnu++23 -Wno-psabi
ASFLAGS := -g $(ARCH) -march=armv5te -mtune=arm946e-s ASFLAGS := -g $(ARCH) -march=armv5te -mtune=arm946e-s

View File

@ -1,6 +1,6 @@
#ifndef VERSION_H #ifndef VERSION_H
#define VERSION_H #define VERSION_H
#define VERSION "v0.1" #define VERSION "v1.0"
#endif // VERSION_H #endif // VERSION_H

27
arm9/src/consoleInfo.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef CONSOLE_INFO_H
#define CONSOLE_INFO_H
#include <array>
#include <cstdint>
#include <string>
#include "sha1digest.h"
#include "nocashFooter.h"
struct consoleInfo {
bool isRetail{true};
bool tmdPatched{false};
bool tmdInvalid{false};
bool tmdFound{true};
bool tmdGood{false};
bool UnlaunchHNAAtmdFound{false};
bool needsNocashFooterToBeWritten{false};
uint32_t launcherVersion{0};
std::string launcherTmdPath;
std::string launcherAppPath;
std::array<uint8_t, 520> recoveryTmdData;
Sha1Digest recoveryTmdDataSha;
NocashFooter nocashFooter;
};
#endif

View File

@ -1,561 +0,0 @@
#include "bgMenu.h"
#include "main.h"
#include "menu.h"
#include "message.h"
#include "nand/nandio.h"
#include "storage.h"
#include "version.h"
#include "unlaunch.h"
#include "nitrofs.h"
#include "deviceList.h"
#include "nocashFooter.h"
volatile bool programEnd = false;
static volatile bool arm7Exiting = false;
static bool unlaunchFound = false;
static bool hnaaUnlaunchFound = false;
static bool retailLauncherTmdPresentAndToBePatched = true;
static bool retailConsole = true;
static UNLAUNCH_VERSION foundUnlaunchInstallerVersion = INVALID;
static bool disableAllPatches = false;
static bool enableSoundAndSplash = false;
static const char* splashSoundBinaryPatchPath = NULL;
static const char* customBgPath = NULL;
volatile bool charging = false;
volatile u8 batteryLevel = 0;
static bool advancedOptionsUnlocked = false;
static bool needsNocashFooterToBeWritten = false;
static bool isLauncherVersionSupported = true;
static NocashFooter computedNocashFooter;
PrintConsole topScreen;
PrintConsole bottomScreen;
enum {
MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL,
MAIN_MENU_CUSTOM_BG,
MAIN_MENU_SOUND_SPLASH_PATCHES,
MAIN_MENU_SAFE_UNLAUNCH_INSTALL,
MAIN_MENU_EXIT,
MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL_NO_BACKUP,
MAIN_MENU_WRITE_NOCASH_FOOTER_ONLY,
MAIN_MENU_TID_PATCHES,
};
static void setupScreens()
{
REG_DISPCNT = MODE_FB0;
VRAM_A_CR = VRAM_ENABLE;
videoSetMode(MODE_0_2D);
videoSetModeSub(MODE_0_2D);
vramSetBankA(VRAM_A_MAIN_BG);
vramSetBankC(VRAM_C_SUB_BG);
consoleInit(&topScreen, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
consoleInit(&bottomScreen, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, false, true);
clearScreen(&bottomScreen);
VRAM_A[100] = 0xFFFF;
}
static int mainMenu(int cursor)
{
//top screen
clearScreen(&topScreen);
iprintf("\t\"Safe\" unlaunch installer\n");
iprintf("\nversion %s\n", VERSION);
iprintf("\n\n\x1B[41mWARNING:\x1B[47m This tool can write to"
"\nyour internal NAND!"
"\n\nThis always has a risk, albeit"
"\nlow, of \x1B[41mbricking\x1B[47m your system"
"\nand should be done with caution!\n");
iprintf("\n\t \x1B[46mhttps://dsi.cfw.guide\x1B[47m\n");
iprintf("\x1b[23;0Hedo9300 - 2024");
//menu
Menu* m = newMenu();
setMenuHeader(m, "MAIN MENU");
char soundPatchesStr[64], tidPatchesStr[32], installUnlaunchStr[32];
sprintf(tidPatchesStr, "Disable all patches: %s",
disableAllPatches ? "On" : "Off");
sprintf(soundPatchesStr, "Enable sound and splash: %s",
enableSoundAndSplash ? "On" : "Off");
if(foundUnlaunchInstallerVersion != INVALID)
{
sprintf(installUnlaunchStr, "Install unlaunch (%s)", getUnlaunchVersionString(foundUnlaunchInstallerVersion));
}
else
{
strcpy(installUnlaunchStr, "Install unlaunch");
}
addMenuItem(m, "Uninstall unlaunch", NULL, unlaunchFound && isLauncherVersionSupported, false);
addMenuItem(m, "Custom background", NULL, foundUnlaunchInstallerVersion != INVALID && isLauncherVersionSupported, true);
addMenuItem(m, soundPatchesStr, NULL, foundUnlaunchInstallerVersion == v2_0 && !disableAllPatches && splashSoundBinaryPatchPath != NULL && isLauncherVersionSupported, false);
addMenuItem(m, installUnlaunchStr, NULL, foundUnlaunchInstallerVersion != INVALID && !unlaunchFound && isLauncherVersionSupported, false);
addMenuItem(m, "Exit", NULL, true, false);
if(!isLauncherVersionSupported)
{
addMenuItem(m, "Uninstall unlaunch no backup", NULL, unlaunchFound, false);
}
else if(advancedOptionsUnlocked)
{
addMenuItem(m, "Uninstall unlaunch no backup", NULL, unlaunchFound, false);
addMenuItem(m, "Write nocash footer", NULL, needsNocashFooterToBeWritten, false);
addMenuItem(m, tidPatchesStr, NULL, (foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0) && isLauncherVersionSupported, false);
}
m->cursor = cursor;
//bottom screen
printMenu(m);
int konamiCode = 0;
bool konamiCodeCooldown = false;
while (!programEnd)
{
swiWaitForVBlank();
scanKeys();
if (moveCursor(m))
printMenu(m);
if (keysDown() & KEY_A)
break;
if(advancedOptionsUnlocked)
continue;
int held = keysHeld();
if ((held & (KEY_L | KEY_R | KEY_Y)) == (KEY_L | KEY_R | KEY_Y))
{
if(held == (KEY_L | KEY_R | KEY_Y) && !konamiCodeCooldown)
{
konamiCodeCooldown = true;
++konamiCode;
}
}
else
{
konamiCodeCooldown = false;
}
if (konamiCode == 5)
{
advancedOptionsUnlocked = true;
// Enabled by default when unsupported
if(isLauncherVersionSupported)
{
addMenuItem(m, "Uninstall unlaunch no backup", NULL, unlaunchFound, false);
}
addMenuItem(m, "Write nocash footer", NULL, needsNocashFooterToBeWritten, false);
addMenuItem(m, tidPatchesStr, NULL, (foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0) && isLauncherVersionSupported, false);
}
}
int result = m->cursor;
freeMenu(m);
return result;
}
static void fifoHandlerPower(u32 value32, void* userdata)
{
if (value32 == 0x54495845) // 'EXIT'
{
programEnd = true;
arm7Exiting = true;
}
}
static void fifoHandlerBattery(u32 value32, void* userdata)
{
batteryLevel = value32 & 0xF;
charging = (value32 & BIT(7)) != 0;
}
int main(int argc, char **argv)
{
keysSetRepeat(25, 5);
setupScreens();
fifoSetValue32Handler(FIFO_USER_01, fifoHandlerPower, NULL);
fifoSetValue32Handler(FIFO_USER_03, fifoHandlerBattery, NULL);
//DSi check
if (!isDSiMode())
{
messageBox("\x1B[31mError:\x1B[33m This app is exclusively for DSi.");
return 0;
}
//setup sd card access
if (!fatInitDefault())
{
messageBox("fatInitDefault()...\x1B[31mFailed\n\x1B[47m");
}
u32 clusterSize = getClusterSizeForPartition("sd:/");
if(clusterSize > 32768)
{
messageBox("Sd card cluster size is too large");
return 0;
}
//setup nand access
if (!fatMountSimple("nand", &io_dsi_nand))
{
messageBox("nand init \x1B[31mfailed\n\x1B[47m");
return 0;
}
NocashFooter footer;
nandio_read_nocash_footer(&footer);
nandio_construct_nocash_footer(&computedNocashFooter);
if(!(needsNocashFooterToBeWritten = !isFooterValid(&footer)))
{
if(memcmp(&footer, &computedNocashFooter, sizeof(footer)) != 0)
{
messageBox("\x1B[31mError:\x1B[33m This console has a\n"
"nocash footer embedded in its\n"
"nand that doesn't match the one\n"
"generated.\n"
"The footer already present will\n"
"be overwritten.");
needsNocashFooterToBeWritten = true;
}
}
while (batteryLevel < 7 && !charging)
{
if (choiceBox("\x1B[47mBattery is too low!\nPlease plug in the console.\n\nContinue?") == NO)
return 0;
}
DeviceList* deviceList = getDeviceList();
const char* installerPath = (argc > 0) ? argv[0] : (deviceList ? deviceList->appname : "sd:/ntrboot.nds");
if (!nitroFSInit(installerPath))
{
messageBox("nitroFSInit()...\x1B[31mFailed\n\x1B[47m");
}
if (fileExists("sd:/unlaunch.dsi"))
{
foundUnlaunchInstallerVersion = loadUnlaunchInstaller("sd:/unlaunch.dsi");
if (foundUnlaunchInstallerVersion == INVALID)
{
messageBox("\x1B[41mWARNING:\x1B[47m Failed to load unlaunch.dsi\n"
"from the root of the sd card.\n"
"Attempting to use the bundled one.");
}
}
if(foundUnlaunchInstallerVersion == INVALID)
{
foundUnlaunchInstallerVersion = loadUnlaunchInstaller("nitro:/unlaunch.dsi");
if (foundUnlaunchInstallerVersion == INVALID)
{
messageBox("\x1B[41mWARNING:\x1B[47m Failed to load bundled unlaunch\n"
"installer.\n"
"Installing unlaunch won't be possible.");
}
}
if (fileExists("sd:/unlaunch-patch.bin")) {
splashSoundBinaryPatchPath = "sd:/unlaunch-patch.bin";
}
else if(fileExists("nitro:/unlaunch-patch.bin"))
{
splashSoundBinaryPatchPath = "nitro:/unlaunch-patch.bin";
}
//check for unlaunch and region
u8 region = 0xff;
char retailLauncherTmdPath[64];
char retailLauncherPath[64];
const char* hnaaTmdPath = "nand:/title/00030017/484e4141/content/title.tmd";
{
FILE* file = fopen("nand:/sys/HWINFO_S.dat", "rb");
bool mainTmdIsPatched = false;
if (file)
{
fseek(file, 0xA0, SEEK_SET);
u32 launcherTid;
fread(&launcherTid, sizeof(u32), 1, file);
fclose(file);
region = launcherTid & 0xFF;
sprintf(retailLauncherTmdPath, "nand:/title/00030017/%08lx/content/title.tmd", launcherTid);
FILE* tmd = fopen(retailLauncherTmdPath, "rb");
unsigned long long tmdSize = getFileSize(tmd);
if(!tmd || tmdSize < 520)
{
//if size isn't 520 then the tmd either is not present, or is already invalid, thus no need to patch
retailLauncherTmdPresentAndToBePatched = false;
}
else
{
if (tmdSize > 520)
{
unlaunchFound = true;
}
else
{
mainTmdIsPatched = isLauncherTmdPatched(retailLauncherTmdPath);
}
fseek(tmd, 0x1DC, SEEK_SET);
unsigned short launcherVersion;
fread(&launcherVersion, sizeof(launcherVersion), 1, tmd);
// Launcher v4, build v1024 (shipped with firmware 1.4.2 (1.4.3 for china and korea)
// will fail to launch if another tmd withouth appropriate application, or an invalid
// tmd (in our case the one installed from unlaunch) is found in the HNAA launcher folder
// there's really no workaround to that, so that specific version is blacklisted and only uninstalling
// an "officially" installed unlaunch without leaving any backup behind will be allowed
if(launcherVersion == 4)
{
isLauncherVersionSupported = false;
messageBox("\x1B[41mWARNING:\x1B[47m This system version\n"
"doesn't support this install\n"
"method, only uninstalling\n"
"unaunch without backups will\n"
"be possible");
}
else if (launcherVersion > 7)
{
char messageBoxError[128];
sprintf(messageBoxError, "\x1B[41mWARNING:\x1B[47m This system version (%d)\n"
"is unknown\n"
"nothing will be done", (int)launcherVersion);
messageBox("\x1B[41mWARNING:\x1B[47m This system version\n"
"is unknown\n"
"nothing will be done");
goto abort;
}
sprintf(retailLauncherPath, "nand:/title/00030017/%08lx/content/0000000%d.app", launcherTid, (int)launcherVersion);
}
if(tmd)
{
fclose(tmd);
}
// HWINFO_S may not always exist (PRE_IMPORT). Fill in defaults if that happens.
}
// I own and know of many people with retail and dev prototypes
// These can normally be identified by having the region set to ALL (0x41)
retailConsole = (region != 0x41 && region != 0xFF);
unsigned long long tmdSize = getFileSizePath(hnaaTmdPath);
if (tmdSize > 520)
{
unlaunchFound = unlaunchFound || (mainTmdIsPatched || !retailConsole);
hnaaUnlaunchFound = true;
}
}
messageBox("\x1B[41mWARNING:\x1B[47m This tool can write to\n"
"your internal NAND!\n\n"
"This always has a risk, albeit\n"
"low, of \x1B[41mbricking\x1B[47m your system\n"
"and should be done with caution!\n\n"
"If you have not yet done so,\n"
"you should make a NAND backup.");
//main menu
int cursor = 0;
while (!programEnd)
{
cursor = mainMenu(cursor);
if(programEnd)
break;
switch (cursor)
{
case MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL:
case MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL_NO_BACKUP:
if(!unlaunchFound)
{
break;
}
bool unsafeUninstall = advancedOptionsUnlocked && cursor == MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL_NO_BACKUP;
if(!isLauncherVersionSupported && !unsafeUninstall)
{
break;
}
if(!nandio_unlock_writing())
{
break;
}
printf("Uninstalling");
if(needsNocashFooterToBeWritten)
{
printf("Writing nocash footer\n");
if(!nandio_write_nocash_footer(&computedNocashFooter))
{
nandio_lock_writing();
messageBox("Failed to write nocash footer");
break;
}
needsNocashFooterToBeWritten = false;
}
if(uninstallUnlaunch(retailConsole, hnaaUnlaunchFound, retailLauncherTmdPath, retailLauncherPath, unsafeUninstall))
{
messageBox("Uninstall successful!\n");
unlaunchFound = false;
}
else
{
messageBox("\x1B[31mError:\x1B[33m Uninstall failed\n");
}
nandio_lock_writing();
printf("Synchronizing FAT tables...\n");
nandio_synchronize_fats();
break;
case MAIN_MENU_CUSTOM_BG:
if(!isLauncherVersionSupported)
{
break;
}
if(foundUnlaunchInstallerVersion == INVALID)
{
break;
}
const char* customBg = backgroundMenu();
if(!customBg)
{
break;
}
if(strcmp(customBg, "default") == 0)
{
customBgPath = NULL;
}
else
{
customBgPath = customBg;
}
break;
case MAIN_MENU_TID_PATCHES:
if(!isLauncherVersionSupported)
{
break;
}
if(advancedOptionsUnlocked && (foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0)) {
disableAllPatches = !disableAllPatches;
}
break;
case MAIN_MENU_SOUND_SPLASH_PATCHES:
if(!isLauncherVersionSupported)
{
break;
}
if(foundUnlaunchInstallerVersion == v2_0 && !disableAllPatches && splashSoundBinaryPatchPath != NULL) {
enableSoundAndSplash = !enableSoundAndSplash;
}
break;
case MAIN_MENU_SAFE_UNLAUNCH_INSTALL:
if(!isLauncherVersionSupported)
{
break;
}
if(unlaunchFound || foundUnlaunchInstallerVersion == INVALID)
{
break;
}
if(choiceBox("Install unlaunch?") == NO)
{
break;
}
if(!retailLauncherTmdPresentAndToBePatched
&& (choiceBox("There doesn't seem to be a launcher.tmd\n"
"file matcing the hwinfo file\n"
"Keep installing?") == NO))
{
break;
}
if(!nandio_unlock_writing())
{
break;
}
printf("Installing\n");
if(needsNocashFooterToBeWritten)
{
printf("Writing nocash footer\n");
if(!nandio_write_nocash_footer(&computedNocashFooter))
{
nandio_lock_writing();
messageBox("Failed to write nocash footer");
break;
}
needsNocashFooterToBeWritten = false;
}
if(installUnlaunch(retailConsole,
retailLauncherTmdPresentAndToBePatched ? retailLauncherTmdPath : NULL,
retailLauncherTmdPresentAndToBePatched ? retailLauncherPath : NULL,
disableAllPatches,
enableSoundAndSplash ? splashSoundBinaryPatchPath : NULL,
customBgPath))
{
messageBox("Install successful!\n");
unlaunchFound = true;
}
else
{
messageBox("\x1B[31mError:\x1B[33m Install failed\n");
}
nandio_lock_writing();
printf("Synchronizing FAT tables...\n");
nandio_synchronize_fats();
break;
case MAIN_MENU_WRITE_NOCASH_FOOTER_ONLY:
if(!needsNocashFooterToBeWritten)
{
break;
}
if(!nandio_write_nocash_footer(&computedNocashFooter))
{
messageBox("Failed to write nocash footer");
break;
}
needsNocashFooterToBeWritten = false;
break;
case MAIN_MENU_EXIT:
programEnd = true;
break;
}
}
abort:
clearScreen(&bottomScreen);
printf("Unmounting NAND...\n");
fatUnmount("nand:");
printf("Merging stages...\n");
nandio_shutdown();
fifoSendValue32(FIFO_USER_02, 0x54495845); // 'EXIT'
while (arm7Exiting)
swiWaitForVBlank();
return 0;
}
void clearScreen(PrintConsole* screen)
{
consoleSelect(screen);
consoleClear();
}

705
arm9/src/main.cpp Normal file
View File

@ -0,0 +1,705 @@
#include <string_view>
#include <string>
#include <format>
#include <exception>
#include <memory>
#include "bgMenu.h"
#include "consoleInfo.h"
#include "main.h"
#include "menu.h"
#include "message.h"
#include "nand/nandio.h"
#include "storage.h"
#include "version.h"
#include "unlaunch.h"
#include "nitrofs.h"
#include "deviceList.h"
#include "nocashFooter.h"
using namespace std::string_view_literals;
volatile bool programEnd = false;
static volatile bool arm7Exiting = false;
static bool retailLauncherTmdPresentAndToBePatched = true;
static UNLAUNCH_VERSION foundUnlaunchInstallerVersion = INVALID;
static bool disableAllPatches = false;
static bool enableSoundAndSplash = false;
static const char* splashSoundBinaryPatchPath = NULL;
static const char* customBgPath = NULL;
volatile bool charging = false;
volatile u8 batteryLevel = 0;
static bool advancedOptionsUnlocked = false;
static bool isLauncherVersionSupported = true;
PrintConsole topScreen;
PrintConsole bottomScreen;
enum {
MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL,
MAIN_MENU_CUSTOM_BG,
MAIN_MENU_SOUND_SPLASH_PATCHES,
MAIN_MENU_SAFE_UNLAUNCH_INSTALL,
MAIN_MENU_EXIT,
MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL_NO_BACKUP,
MAIN_MENU_WRITE_NOCASH_FOOTER_ONLY,
MAIN_MENU_TID_PATCHES,
};
static void setupScreens()
{
REG_DISPCNT = MODE_FB0;
VRAM_A_CR = VRAM_ENABLE;
videoSetMode(MODE_0_2D);
videoSetModeSub(MODE_0_2D);
vramSetBankA(VRAM_A_MAIN_BG);
vramSetBankC(VRAM_C_SUB_BG);
consoleInit(&topScreen, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true);
consoleInit(&bottomScreen, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, false, true);
clearScreen(&bottomScreen);
VRAM_A[100] = 0xFFFF;
}
static int mainMenu(const consoleInfo& info, int cursor)
{
const auto tidPatchesSupported = (foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0) && isLauncherVersionSupported;
//top screen
clearScreen(&topScreen);
iprintf("\t\"Safe\" unlaunch installer\n");
iprintf("\nversion %s\n", VERSION);
iprintf("\n\n\x1B[41mWARNING:\x1B[47m This tool can write to"
"\nyour internal NAND!"
"\n\nThis always has a risk, albeit"
"\nlow, of \x1B[41mbricking\x1B[47m your system"
"\nand should be done with caution!\n");
iprintf("\n\t \x1B[46mhttps://dsi.cfw.guide\x1B[47m\n");
iprintf("\x1b[23;0Hedo9300 - 2025");
//menu
Menu* m = newMenu();
setMenuHeader(m, "MAIN MENU");
auto [restore_string, restore_string_no_backup] = [&]{
if(info.tmdInvalid) {
return std::make_pair("Restore launcher tmd", "Restore launcher tmd no backup");
}
return std::make_pair("Uninstall unlaunch", "Uninstall unlaunch no backup");
}();
char soundPatchesStr[64], tidPatchesStr[32], installUnlaunchStr[32];
sprintf(tidPatchesStr, "Disable all patches: %s",
disableAllPatches ? "On" : "Off");
sprintf(soundPatchesStr, "Enable sound and splash: %s",
enableSoundAndSplash ? "On" : "Off");
if(foundUnlaunchInstallerVersion != INVALID)
{
sprintf(installUnlaunchStr, "Install unlaunch (%s)", getUnlaunchVersionString(foundUnlaunchInstallerVersion));
}
else
{
strcpy(installUnlaunchStr, "Install unlaunch");
}
addMenuItem(m, restore_string, NULL, (info.tmdInvalid || info.tmdPatched) && isLauncherVersionSupported, false);
addMenuItem(m, "Custom background", NULL, foundUnlaunchInstallerVersion != INVALID && isLauncherVersionSupported, true);
addMenuItem(m, soundPatchesStr, NULL, foundUnlaunchInstallerVersion == v2_0 && !disableAllPatches && splashSoundBinaryPatchPath != NULL && isLauncherVersionSupported, false);
addMenuItem(m, installUnlaunchStr, NULL, foundUnlaunchInstallerVersion != INVALID && !(info.tmdInvalid || info.tmdPatched) && isLauncherVersionSupported, false);
addMenuItem(m, "Exit", NULL, true, false);
if(!isLauncherVersionSupported)
{
addMenuItem(m, restore_string_no_backup, NULL, (info.tmdInvalid || info.tmdPatched), false);
}
else if(advancedOptionsUnlocked)
{
addMenuItem(m, restore_string_no_backup, NULL, (info.tmdInvalid || info.tmdPatched), false);
addMenuItem(m, "Write nocash footer", NULL, info.needsNocashFooterToBeWritten, false);
addMenuItem(m, tidPatchesStr, NULL, tidPatchesSupported, false);
}
m->cursor = cursor;
//bottom screen
printMenu(m);
int konamiCode = 0;
bool konamiCodeCooldown = false;
while (!programEnd)
{
swiWaitForVBlank();
scanKeys();
if (moveCursor(m))
printMenu(m);
if (keysDown() & KEY_A)
break;
if(advancedOptionsUnlocked)
continue;
int held = keysHeld();
if ((held & (KEY_L | KEY_R | KEY_Y)) == (KEY_L | KEY_R | KEY_Y))
{
if(held == (KEY_L | KEY_R | KEY_Y) && !konamiCodeCooldown)
{
konamiCodeCooldown = true;
++konamiCode;
}
}
else
{
konamiCodeCooldown = false;
}
if (konamiCode == 5)
{
advancedOptionsUnlocked = true;
// Enabled by default when unsupported
if(isLauncherVersionSupported)
{
addMenuItem(m, "Uninstall unlaunch no backup", NULL, (info.tmdInvalid || info.tmdPatched), false);
}
addMenuItem(m, "Write nocash footer", NULL, info.needsNocashFooterToBeWritten, false);
addMenuItem(m, tidPatchesStr, NULL, tidPatchesSupported, false);
}
}
int result = m->cursor;
freeMenu(m);
return result;
}
void setup() {
keysSetRepeat(25, 5);
setupScreens();
fifoSetValue32Handler(FIFO_USER_01, [](u32 value32, void*) {
if (value32 != 0x54495845) // 'EXIT'
return;
programEnd = true;
arm7Exiting = true;
}, NULL);
fifoSetValue32Handler(FIFO_USER_03, [](u32 value32, void*) {
batteryLevel = value32 & 0xF;
charging = (value32 & BIT(7)) != 0;
}, NULL);
//DSi check
if (!isDSiMode())
{
messageBox("\x1B[31mError:\x1B[33m This app is exclusively for DSi.");
exit(0);
}
//setup sd card access
if (!fatInitDefault())
{
messageBox("fatInitDefault()...\x1B[31mFailed\n\x1B[47m");
}
u32 clusterSize = getClusterSizeForPartition("sd:/");
if(clusterSize > 32768)
{
messageBox("Sd card cluster size is too large");
exit(0);
}
//setup nand access
if (!fatMountSimple("nand", &io_dsi_nand))
{
messageBox("nand init \x1B[31mfailed\n\x1B[47m");
exit(0);
}
}
void checkNocashFooter(consoleInfo& info) {
NocashFooter footer;
nandio_read_nocash_footer(&footer);
nandio_construct_nocash_footer(&info.nocashFooter);
info.needsNocashFooterToBeWritten = !isFooterValid(&footer);
if(!info.needsNocashFooterToBeWritten)
{
if(memcmp(&footer, &info.nocashFooter, sizeof(footer)) != 0)
{
messageBox("\x1B[31mError:\x1B[33m This console has a\n"
"nocash footer embedded in its\n"
"nand that doesn't match the one\n"
"generated.\n"
"The footer already present will\n"
"be overwritten.");
info.needsNocashFooterToBeWritten = true;
}
}
}
bool writeNocashFooter(consoleInfo& info) {
if(!info.needsNocashFooterToBeWritten)
return true;
if(!nandio_unlock_writing())
return false;
printf("Writing nocash footer\n");
auto res = nandio_write_nocash_footer(&info.nocashFooter);
nandio_lock_writing();
if(!res)
{
messageBox("Failed to write nocash footer");
return false;
}
info.needsNocashFooterToBeWritten = false;
return true;
}
void waitForBatteryChargedEnough() {
while (batteryLevel < 7 && !charging)
{
if (choiceBox("\x1B[47mBattery is too low!\nPlease plug in the console.\n\nContinue?") == NO)
exit(0);
}
}
const char* getInstallerPath(int argc, char **argv) {
if(argc > 0)
return argv[0];
DeviceList* deviceList = getDeviceList();
if(deviceList) return deviceList->appname;
return "sd:/ntrboot.nds";
}
void loadUnlaunchInstaller() {
if (fileExists("sd:/unlaunch.dsi"))
{
foundUnlaunchInstallerVersion = loadUnlaunchInstaller("sd:/unlaunch.dsi");
if(foundUnlaunchInstallerVersion != INVALID)
return;
messageBox("\x1B[41mWARNING:\x1B[47m Failed to load unlaunch.dsi\n"
"from the root of the sd card.\n"
"Attempting to use the bundled one.");
}
foundUnlaunchInstallerVersion = loadUnlaunchInstaller("nitro:/unlaunch.dsi");
if(foundUnlaunchInstallerVersion != INVALID)
return;
messageBox("\x1B[41mWARNING:\x1B[47m Failed to load bundled unlaunch\n"
"installer.\n"
"Installing unlaunch won't be possible.");
}
void loadUnlaunchInstallerPatch() {
if (fileExists("sd:/unlaunch-patch.bin")) {
splashSoundBinaryPatchPath = "sd:/unlaunch-patch.bin";
}
else if(fileExists("nitro:/unlaunch-patch.bin"))
{
splashSoundBinaryPatchPath = "nitro:/unlaunch-patch.bin";
}
}
void parseLauncherInfo(std::string_view launcher_tid_str, consoleInfo& info) {
auto launcher_content_path = std::format("nand:/title/00030017/{}/content", launcher_tid_str);
auto [tmd_found, expected_launcher_build, retailLauncherPath] = [&] {
std::shared_ptr<DIR> pdir{opendir(launcher_content_path.c_str()), closedir};
if (!pdir)
throw std::runtime_error(std::format("Could not open launcher title directory ({})", launcher_content_path));
dirent* pent;
std::optional<std::pair<uint32_t, std::string>> foundApp;
bool tmdFound;
while((pent = readdir(pdir.get())) != nullptr) {
if(foundApp && tmdFound) {
break;
}
if(pent->d_type == DT_DIR)
continue;
std::string_view filename{pent->d_name};
if(filename == "title.tmd") {
tmdFound = true;
continue;
}
if(filename.size() != 12 || !filename.ends_with(".app") || !filename.starts_with("0000000"))
continue;
auto launcher_app_version = static_cast<uint16_t>(static_cast<unsigned char>(filename[7]) - static_cast<unsigned char>('0'));
if(launcher_app_version > 7)
throw std::runtime_error(std::format("Found an unsupported launcher version: {}", launcher_app_version));
foundApp = std::make_pair(static_cast<uint32_t>(256 * launcher_app_version), std::string{filename});
}
if(!foundApp)
throw std::runtime_error("Launcher app not found");
const auto& [launcher_build, launcher_app_name] = *foundApp;
return std::make_tuple(tmdFound, launcher_build, std::format("{}/{}", launcher_content_path, launcher_app_name));
}();
if((info.tmdFound = tmd_found)) {
const auto recoveryTmdPath = std::format("nitro:/{}/tmd.{}", launcher_tid_str, static_cast<int>(expected_launcher_build));
info.launcherTmdPath = std::format("{}/title.tmd", launcher_content_path);
info.recoveryTmdDataSha = [&] -> Sha1Digest {
auto file = fopen(std::format("{}.sha1", recoveryTmdPath).data(), "rb");
if(!file)
throw std::runtime_error("Good tmd sha1 not found");
char sha1StrBuff[41]{};
auto read = fread(sha1StrBuff, sizeof(sha1StrBuff) - 1, 1, file);
fclose(file);
if(read != 1)
throw std::runtime_error("Failed to parse good tmd's sha1 file");
return {sha1StrBuff};
}();
auto patchedTmdSha1 = [&] -> Sha1Digest {
auto file = fopen(std::format("{}.patch.sha1", recoveryTmdPath).data(), "rb");
if(!file)
throw std::runtime_error("Patched tmd sha1 not found");
char sha1StrBuff[41]{};
auto read = fread(sha1StrBuff, sizeof(sha1StrBuff) - 1, 1, file);
fclose(file);
if(read != 1)
throw std::runtime_error("Failed to parse patched tmd's sha1 file");
return {sha1StrBuff};
}();
info.recoveryTmdData = [&] {
auto* sourceTmd = fopen(recoveryTmdPath.data(), "rb");
std::array<uint8_t, 520> ret;
auto read = fread(ret.data(), ret.size(), 1, sourceTmd);
fclose(sourceTmd);
if(read != 1)
{
throw std::runtime_error("Failed to read good tmd's buffer");
}
Sha1Digest digest;
swiSHA1Calc(digest.data(), ret.data(), ret.size());
if(digest != info.recoveryTmdDataSha)
{
throw std::runtime_error("Good tmd's sha mismatching");
}
return ret;
}();
std::shared_ptr<FILE> tmd{fopen(info.launcherTmdPath.data(), "rb"), fclose};
if(!tmd) {
info.tmdFound = false;
} else if(auto tmdSize = getFileSize(tmd.get()); tmdSize < 520) {
//if size isn't at least 520 then the tmd is already invalid
info.tmdInvalid = true;
} else {
info.launcherAppPath = retailLauncherPath;
if(tmdSize > 520) {
info.tmdInvalid = true;
}
else
{
Sha1Digest digest;
calculateFileSha1(tmd.get(), &digest);
if(digest == info.recoveryTmdDataSha){
info.tmdGood = true;
} else if(digest == patchedTmdSha1) {
info.tmdPatched = true;
} else {
info.tmdInvalid = true;
}
}
if(!info.tmdInvalid) {
fseek(tmd.get(), 0x1DC, SEEK_SET);
uint16_t launcherVersion;
fread(&launcherVersion, sizeof(launcherVersion), 1, tmd.get());
if(static_cast<uint32_t>(launcherVersion) * 256 != expected_launcher_build) {
throw std::runtime_error("Launcher version found doesn't match with the one in the tmd");
}
info.launcherVersion = launcherVersion;
}
}
if(info.tmdInvalid || !info.tmdFound) {
// if the tmd is invalid, don't read the launcher version from it and assume it's the one
// matching the app file
info.launcherVersion = expected_launcher_build / 256;
}
}
}
void retrieveInstalledLauncherInfo(consoleInfo& info) {
static constexpr auto hnaaTmdPath = "nand:/title/00030017/484e4141/content/title.tmd"sv;
const auto [launcher_tid_str, region] = [] -> std::pair<std::string, u8> {
uint32_t launcherTid;
{
auto* file = fopen("nand:/sys/HWINFO_S.dat", "rb");
if(!file)
return std::make_pair("", static_cast<u8>(0xFF));
fseek(file, 0xA0, SEEK_SET);
fread(&launcherTid, sizeof(uint32_t), 1, file);
fclose(file);
}
return std::make_pair(std::format("{:08x}", launcherTid), static_cast<u8>(launcherTid & 0xFF));
}();
// I own and know of many people with retail and dev prototypes
// These can normally be identified by having the region set to ALL (0x41)
info.isRetail = (region != 0x41 && region != 0xFF);
//check for unlaunch and region
if (info.isRetail && launcher_tid_str.size() != 0) {
parseLauncherInfo(launcher_tid_str, info);
} else {
// HWINFO_S may not always exist (PRE_IMPORT). Fill in defaults if that happens.
(void)0;
}
if (auto tmdSize = getFileSizePath(hnaaTmdPath.data()); tmdSize > 520) {
info.UnlaunchHNAAtmdFound = true;
}
}
void uninstall(consoleInfo& info, bool noBackup) {
if(!(info.tmdInvalid || info.tmdPatched))
{
return;
}
bool unsafeUninstall = advancedOptionsUnlocked && noBackup;
if(!isLauncherVersionSupported && !unsafeUninstall)
{
return;
}
printf("Uninstalling");
if(!writeNocashFooter(info))
{
return;
}
if(!nandio_unlock_writing())
{
return;
}
if(uninstallUnlaunch(info, unsafeUninstall))
{
messageBox("Uninstall successful!\n");
info.tmdInvalid = false;
info.tmdPatched = false;
info.tmdGood = true;
info.UnlaunchHNAAtmdFound = !unsafeUninstall;
}
else
{
messageBox("\x1B[31mError:\x1B[33m Uninstall failed\n");
}
nandio_lock_writing();
printf("Synchronizing FAT tables...\n");
nandio_synchronize_fats();
}
void install(consoleInfo& info) {
if(!isLauncherVersionSupported)
{
return;
}
if(info.tmdInvalid || info.tmdPatched || !info.tmdGood || foundUnlaunchInstallerVersion == INVALID)
{
return;
}
if(choiceBox("Install unlaunch?") == NO)
{
return;
}
if(!retailLauncherTmdPresentAndToBePatched
&& (choiceBox("There doesn't seem to be a launcher.tmd\n"
"file matcing the hwinfo file\n"
"Keep installing?") == NO))
{
return;
}
printf("Installing\n");
if(!writeNocashFooter(info))
{
return;
}
if(!nandio_unlock_writing())
{
return;
}
if(installUnlaunch(info, disableAllPatches,
enableSoundAndSplash ? splashSoundBinaryPatchPath : NULL,
customBgPath))
{
messageBox("Install successful!\n");
info.tmdGood = false;
info.tmdPatched = true;
info.UnlaunchHNAAtmdFound = true;
}
else
{
messageBox("\x1B[31mError:\x1B[33m Install failed\n");
}
nandio_lock_writing();
printf("Synchronizing FAT tables...\n");
nandio_synchronize_fats();
}
void customBg() {
if(!isLauncherVersionSupported)
{
return;
}
if(foundUnlaunchInstallerVersion == INVALID)
{
return;
}
const char* customBg = backgroundMenu();
if(!customBg)
{
return;
}
if(strcmp(customBg, "default") == 0)
{
customBgPath = NULL;
}
else
{
customBgPath = customBg;
}
}
void doMainMenu(consoleInfo& info) {
int cursor = 0;
while(!programEnd)
{
cursor = mainMenu(info, cursor);
if(programEnd)
break;
switch (cursor)
{
case MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL:
case MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL_NO_BACKUP:
{
uninstall(info, cursor == MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL_NO_BACKUP);
}
break;
case MAIN_MENU_CUSTOM_BG:
{
customBg();
}
break;
case MAIN_MENU_TID_PATCHES:
if(!isLauncherVersionSupported)
{
break;
}
if(advancedOptionsUnlocked && (foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0)) {
disableAllPatches = !disableAllPatches;
}
break;
case MAIN_MENU_SOUND_SPLASH_PATCHES:
if(!isLauncherVersionSupported)
{
break;
}
if(foundUnlaunchInstallerVersion == v2_0 && !disableAllPatches && splashSoundBinaryPatchPath != NULL) {
enableSoundAndSplash = !enableSoundAndSplash;
}
break;
case MAIN_MENU_SAFE_UNLAUNCH_INSTALL:
{
install(info);
}
break;
case MAIN_MENU_WRITE_NOCASH_FOOTER_ONLY:
(void)writeNocashFooter(info);
break;
case MAIN_MENU_EXIT:
programEnd = true;
return;
}
}
}
int main(int argc, char **argv)
{
setup();
if (!nitroFSInit(getInstallerPath(argc, argv)))
{
messageBox("nitroFSInit()...\x1B[31mFailed\n\x1B[47m");
}
loadUnlaunchInstaller();
loadUnlaunchInstallerPatch();
consoleInfo info;
try {
retrieveInstalledLauncherInfo(info);
checkNocashFooter(info);
// Launcher v4, build v1024 (shipped with firmware 1.4.2 (1.4.3 for china and korea)
// will fail to launch if another tmd withouth appropriate application, or an invalid
// tmd (in our case the one installed from unlaunch) is found in the HNAA launcher folder
// there's really no workaround to that, so that specific version is blacklisted and only uninstalling
// an "officially" installed unlaunch without leaving any backup behind will be allowed
if(info.launcherVersion == 4) {
isLauncherVersionSupported = false;
messageBox("\x1B[41mWARNING:\x1B[47m This system version\n"
"doesn't support this install\n"
"method, only uninstalling\n"
"unaunch without backups will\n"
"be possible");
}
messageBox("\x1B[41mWARNING:\x1B[47m This tool can write to\n"
"your internal NAND!\n\n"
"This always has a risk, albeit\n"
"low, of \x1B[41mbricking\x1B[47m your system\n"
"and should be done with caution!\n\n"
"If you have not yet done so,\n"
"you should make a NAND backup.");
waitForBatteryChargedEnough();
doMainMenu(info);
} catch (const std::exception& e) {
messageBox(e.what());
}
clearScreen(&bottomScreen);
printf("Unmounting NAND...\n");
fatUnmount("nand:");
printf("Merging stages...\n");
nandio_shutdown();
fifoSendValue32(FIFO_USER_02, 0x54495845); // 'EXIT'
while (arm7Exiting)
swiWaitForVBlank();
return 0;
}
void clearScreen(PrintConsole* screen)
{
consoleSelect(screen);
consoleClear();
}

View File

@ -8,6 +8,8 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <memory>
#include <format>
static char unlaunchInstallerBuffer[0x30000]; static char unlaunchInstallerBuffer[0x30000];
static char ogUnlaunchInstallerBuffer[0x30000]; static char ogUnlaunchInstallerBuffer[0x30000];
@ -51,23 +53,9 @@ bool isValidUnlaunchInstallerSize(size_t size)
return size == 163320 /*1.8*/ || size == 196088 /*1.9, 2.0*/; return size == 163320 /*1.8*/ || size == 196088 /*1.9, 2.0*/;
} }
bool isLauncherTmdPatched(const char* path)
{
FILE* launcherTmd = fopen(path, "rb");
if(!launcherTmd)
{
return false;
}
fseek(launcherTmd, 0x190, SEEK_SET);
char c;
fread(&c, 1, 1, launcherTmd);
fclose(launcherTmd);
return c == 0x47;
}
static bool removeHnaaLauncher() static bool removeHnaaLauncher()
{ {
auto* errString = [] -> const char* { auto* errString = [] -> const char* {
if(!toggleFileReadOnly(hnaaTmdPath, false)) if(!toggleFileReadOnly(hnaaTmdPath, false))
{ {
return "\x1B[31mError:\x1B[33m Failed to mark unlaunch's title.tmd as writable\nLeaving as is\n"; return "\x1B[31mError:\x1B[33m Failed to mark unlaunch's title.tmd as writable\nLeaving as is\n";
@ -94,92 +82,85 @@ static bool removeHnaaLauncher()
return true; return true;
} }
static bool restoreMainTmd(const char* path, bool hasHNAABackup, bool removeHNAABackup) static bool restoreMainTmd(const consoleInfo& info, bool removeHNAABackup)
{ {
FILE* launcherTmd = fopen(path, "r+b"); std::shared_ptr<FILE> launcherTmdSptr{fopen(info.launcherTmdPath.data(), "r+b"), fclose};
if(!launcherTmd) if(!launcherTmdSptr)
{ {
messageBox("\x1B[31mError:\x1B[33m Failed to open default launcher's title.tmd\n"); messageBox("\x1B[31mError:\x1B[33m Failed to open default launcher's title.tmd\n");
return false; return false;
} }
// Set back the title.tmd's title id from GNXX to HNXX FILE* launcherTmd = launcherTmdSptr.get();
fseek(launcherTmd, 0x190, SEEK_SET);
char c;
fread(&c, 1, 1, launcherTmd);
//if byte is not what we expect, the install method was different
if(c == 0x47)
{
fseek(launcherTmd, -1, SEEK_CUR);
c = 0x48;
fwrite(&c, 1, 1, launcherTmd);
fclose(launcherTmd);
if(removeHNAABackup && hasHNAABackup)
{
removeHnaaLauncher();
}
}
else if(c != 0x47)
{
if (getFileSize(launcherTmd) > 520) {
// Remove unlaunch if it already exists on the main launcher tmd.
// If we don't do this and unlaunch is on the tmd, it will take over and prevent loading HNAA
// This is also a good idea to make sure the tmd is 520b. // If the tmd is patched, assume the HNAA backup is already set in place.
// You will have a much higher brick risk if something goes wrong with a tmd over 520b. if(info.tmdPatched) {
// See: http://docs.randommeaninglesscharacters.com/unlaunch.html fseek(launcherTmd, 0x190, SEEK_SET);
if(!hasHNAABackup && !removeHNAABackup) // Set back the title.tmd's title id from GNXX to HNXX
{ char c = 0x48;
auto choiceString = [&]{ fwrite(&c, 1, 1, launcherTmd);
if(installerVersion != INVALID) fflush(launcherTmd);
return "Unlaunch was installed with the\n" } else if(!info.tmdGood || info.tmdInvalid) {
"legacy method.\n" // The tmd isn't good, it either has the wrong size, or the hash didn't match
"Before uninstalling it, a\n" // and it wasn't patched with the new method
"failsafe installation will be\n" // Install the hnaa backup if not found and then truncate the tmd to 520b
"created.\n" // before restoring it
"Proceed?"; if(!info.UnlaunchHNAAtmdFound && !removeHNAABackup)
return "Unlaunch was installed with the\n" {
"legacy method\n" auto choiceString = [&]{
"But a failsafe installation\n" if(installerVersion != INVALID)
"cannot be created since no valid\n" return "Unlaunch was installed with the\n"
"unlaunch installer was provided.\n" "legacy method.\n"
"Proceed anyways?"; "Before uninstalling it, a\n"
}(); "failsafe installation will be\n"
if(choiceBox(choiceString) == NO) "created.\n"
{ "Proceed?";
fclose(launcherTmd); return "Unlaunch was installed with the\n"
return false; "legacy method\n"
} "But a failsafe installation\n"
if(installerVersion != INVALID) "cannot be created since no valid\n"
{ "unlaunch installer was provided.\n"
if(!writeUnlaunchToHNAAFolder()) "Proceed anyways?";
{ }();
if(choiceBox("Failsafe installation couldn't\n" if(choiceBox(choiceString) == NO)
"be copmleted.\n" {
"Proceed anyways?") == NO) return false;
{ }
fclose(launcherTmd); if(installerVersion != INVALID)
return false; {
} if(!writeUnlaunchToHNAAFolder())
} {
} if(choiceBox("Failsafe installation couldn't\n"
} "be copmleted.\n"
if(removeHNAABackup && hasHNAABackup) "Proceed anyways?") == NO)
{ {
removeHnaaLauncher(); return false;
} }
if (ftruncate(fileno(launcherTmd), 520) != 0) { }
messageBox("\x1B[31mError:\x1B[33m Failed to remove unlaunch\n"); }
fclose(launcherTmd); }
return false; if (ftruncate(fileno(launcherTmd), 520) != 0) {
} messageBox("\x1B[31mError:\x1B[33m Failed to remove unlaunch\n");
fclose(launcherTmd); return false;
return true; }
} }
messageBox("\x1B[31mError:\x1B[33m Unlaunch was installed with an\nunknown method\naborting\n");
fclose(launcherTmd); Sha1Digest digest;
return false; calculateFileSha1(launcherTmd, &digest);
}
return true; // the tmd still doesn't match, write a known good one
if(digest != info.recoveryTmdDataSha) {
fseek(launcherTmd, 0, SEEK_SET);
auto written = fwrite(info.recoveryTmdData.data(), info.recoveryTmdData.size(), 1, launcherTmd);
if(written != 1) {
messageBox("\x1B[31mError:\x1B[33m Failed to remove unlaunch\n");
return false;
}
}
if(removeHNAABackup && info.UnlaunchHNAAtmdFound)
{
return removeHnaaLauncher();
}
return true;
} }
static bool patchMainTmd(const char* path) static bool patchMainTmd(const char* path)
@ -225,15 +206,21 @@ static bool restoreProtoTmd(const char* path)
return true; return true;
} }
bool uninstallUnlaunch(bool retailConsole, bool hasHNAABackup, const char* retailLauncherTmdPath, const char* retailLauncherPath, bool removeHNAABackup) bool uninstallUnlaunch(const consoleInfo& info, bool removeHNAABackup)
{ {
// TODO: handle retailLauncherTmdPresentAndToBePatched = false on retail consoles // TODO: handle retailLauncherTmdPresentAndToBePatched = false on retail consoles
if (retailConsole) { if (info.isRetail) {
if (!toggleFileReadOnly(retailLauncherTmdPath, false) || !toggleFileReadOnly(retailLauncherPath, false)) if(!toggleFileReadOnly(info.launcherTmdPath.data(), false))
{
messageBox(std::format("\x1B[31mError:\x1B[33m Failed to make {} writable\n", info.launcherTmdPath).data());
return false;
}
if(!toggleFileReadOnly(info.launcherAppPath.data(), false))
{ {
messageBox(std::format("\x1B[31mError:\x1B[33m Failed to make {} writable\n", info.launcherAppPath).data());
return false; return false;
} }
if (!restoreMainTmd(retailLauncherTmdPath, hasHNAABackup, removeHNAABackup)) if (!restoreMainTmd(info, removeHNAABackup))
{ {
return false; return false;
} }
@ -312,7 +299,7 @@ static bool writeUnlaunchToHNAAFolder()
return true; return true;
} }
static bool installUnlaunchRetailConsole(const char* retailLauncherTmdPath, const char* retailLauncherPath) static bool installUnlaunchRetailConsole(const consoleInfo& info)
{ {
if(!writeUnlaunchToHNAAFolder()) if(!writeUnlaunchToHNAAFolder())
return false; return false;
@ -320,20 +307,20 @@ static bool installUnlaunchRetailConsole(const char* retailLauncherTmdPath, cons
//Finally patch the default launcher tmd to be invalid //Finally patch the default launcher tmd to be invalid
//If there isn't a title.tmd matching the language region in the hwinfo //If there isn't a title.tmd matching the language region in the hwinfo
// nothing else has to be done, could be a language patch, or a dev system, the user will know what they have done // nothing else has to be done, could be a language patch, or a dev system, the user will know what they have done
if (retailLauncherTmdPath) if (!info.tmdFound)
{ return true;
// Set tmd as writable in case unlaunch was already installed through the old method
if(!toggleFileReadOnly(retailLauncherTmdPath, false) || !patchMainTmd(retailLauncherTmdPath)) // Set tmd as writable in case unlaunch was already installed through the old method
{ if(!toggleFileReadOnly(info.launcherTmdPath.data(), false) || !patchMainTmd(info.launcherTmdPath.data()))
removeHnaaLauncher(); {
return false; removeHnaaLauncher();
} return false;
if (!toggleFileReadOnly(retailLauncherTmdPath, true) || !toggleFileReadOnly(retailLauncherPath, true)) }
{ if (!toggleFileReadOnly(info.launcherTmdPath.data(), true) || !toggleFileReadOnly(info.launcherAppPath.data(), true))
messageBox("\x1B[31mError:\x1B[33m Failed to mark default launcher's title.tmd\nas read only, install might be unstable\n"); {
} messageBox("\x1B[31mError:\x1B[33m Failed to mark default launcher's title.tmd\nas read only, install might be unstable\n");
} }
return true; return true;
} }
static bool installUnlaunchProtoConsole(void) static bool installUnlaunchProtoConsole(void)
@ -381,9 +368,9 @@ static bool installUnlaunchProtoConsole(void)
return true; return true;
} }
static bool readUnlaunchInstaller(const char* path) static bool readUnlaunchInstaller(std::string_view path)
{ {
FILE* unlaunchInstaller = fopen(path, "rb"); FILE* unlaunchInstaller = fopen(path.data(), "rb");
if (!unlaunchInstaller) if (!unlaunchInstaller)
{ {
messageBox("\x1B[31mError:\x1B[33m Failed to open unlaunch installer\n"); messageBox("\x1B[31mError:\x1B[33m Failed to open unlaunch installer\n");
@ -545,7 +532,7 @@ static bool patchUnlaunchInstaller(bool disableAllPatches, const char* splashSou
return true; return true;
} }
UNLAUNCH_VERSION loadUnlaunchInstaller(const char* path) UNLAUNCH_VERSION loadUnlaunchInstaller(std::string_view path)
{ {
if(readUnlaunchInstaller(path) && verifyUnlaunchInstaller()) if(readUnlaunchInstaller(path) && verifyUnlaunchInstaller())
{ {
@ -569,16 +556,16 @@ const char* getUnlaunchVersionString(UNLAUNCH_VERSION version)
return unlaunchVersionStrings[version]; return unlaunchVersionStrings[version];
} }
bool installUnlaunch(bool retailConsole, const char* retailLauncherTmdPath, const char* retailLauncherPath, bool disableAllPatches, const char* splashSoundBinaryPatchPath, const char* customBackgroundPath) bool installUnlaunch(const consoleInfo& info, bool disableAllPatches, const char* splashSoundBinaryPatchPath, const char* customBackgroundPath)
{ {
if (installerVersion == INVALID || !patchUnlaunchInstaller(disableAllPatches, splashSoundBinaryPatchPath, customBackgroundPath)) if (installerVersion == INVALID || !patchUnlaunchInstaller(disableAllPatches, splashSoundBinaryPatchPath, customBackgroundPath))
return false; return false;
// Treat protos differently // Treat protos differently
if (!retailConsole) if (!info.isRetail)
{ {
return installUnlaunchProtoConsole(); return installUnlaunchProtoConsole();
} }
// Do things normally for production units // Do things normally for production units
return installUnlaunchRetailConsole(retailLauncherTmdPath, retailLauncherPath); return installUnlaunchRetailConsole(info);
} }

View File

@ -1,10 +1,8 @@
#ifndef UNLAUNCH_H #ifndef UNLAUNCH_H
#define UNLAUNCH_H #define UNLAUNCH_H
#include <stdbool.h> #include <string_view>
#ifdef __cplusplus #include "consoleInfo.h"
extern "C" {
#endif
typedef enum UNLAUNCH_VERSION { typedef enum UNLAUNCH_VERSION {
v1_8, v1_8,
@ -15,15 +13,9 @@ typedef enum UNLAUNCH_VERSION {
const char* getUnlaunchVersionString(UNLAUNCH_VERSION); const char* getUnlaunchVersionString(UNLAUNCH_VERSION);
bool uninstallUnlaunch(bool notProto, bool hasHNAABackup, const char* retailLauncherTmdPath, const char* retailLauncherPath, bool removeHNAABackup); bool uninstallUnlaunch(const consoleInfo& info, bool removeHNAABackup);
bool installUnlaunch(bool retailConsole, const char* retailLauncherTmdPath, const char* retailLauncherPath, bool disableAllPatches, const char* splashSoundBinaryPatchPath, const char* customBackgroundPath); bool installUnlaunch(const consoleInfo& info, bool disableAllPatches, const char* splashSoundBinaryPatchPath, const char* customBackgroundPath);
bool isLauncherTmdPatched(const char* path); UNLAUNCH_VERSION loadUnlaunchInstaller(std::string_view path);
UNLAUNCH_VERSION loadUnlaunchInstaller(const char* path);
#ifdef __cplusplus
}
#endif
#endif #endif

Binary file not shown.

View File

@ -0,0 +1 @@
d20ef5520ef3df8bc418f46075cc5e6865ed4b6e

View File

@ -0,0 +1 @@
88076c8d9e8330e43e32bba7c24fb2970d16241e

Binary file not shown.

View File

@ -0,0 +1 @@
7fc30f65d75ca08ff6c356527a8cc4f62669a69e

View File

@ -0,0 +1 @@
58e0db23ee9b0cae9d7e83fd39b1781a7eebf3bb

Binary file not shown.

View File

@ -0,0 +1 @@
dab8a54b21f6c76062b0ad2a2e195a09c27960fd

View File

@ -0,0 +1 @@
7c57ec724652b98923b2111150062657119644c5

Binary file not shown.

View File

@ -0,0 +1 @@
0358cb04f28d038cf6337a9d05ae58b3ad86af59

View File

@ -0,0 +1 @@
e0a43818affb9c2849230abba12bc59fae48aa69

BIN
nitrofiles/484e4143/tmd.512 Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
4e350d55f0b134748ddaf1ff1eef99de4c7dfa85

View File

@ -0,0 +1 @@
76727f47fa55a06d0b684745c5e6cdd5f596c4e5

BIN
nitrofiles/484e4143/tmd.768 Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
b1d18a727dae139451a6e2ca4b89dd22253d4639

View File

@ -0,0 +1 @@
ebd7cff8b0d3ccca8579872b29dc20cc3a0827d1

Binary file not shown.

View File

@ -0,0 +1 @@
a91f95d68dccd359987ef7e32ec2221148153da6

View File

@ -0,0 +1 @@
7795346d34093c0d73db587f6c5fd8510b947ec7

Binary file not shown.

View File

@ -0,0 +1 @@
4c482027655615abe80ed32a26f064fd97caf80c

View File

@ -0,0 +1 @@
978f1a3b26a4f983a7e305d1a89c73d0890e6683

Binary file not shown.

View File

@ -0,0 +1 @@
c758f170bc58b6a2bc60cd521456b76a0304fe2c

View File

@ -0,0 +1 @@
ca573c0014319ce6a5e3ead6197d07c84783f21d

Binary file not shown.

View File

@ -0,0 +1 @@
4702ac21227a9a1e2d46b6c336e8dc99b1ec25bf

View File

@ -0,0 +1 @@
a297f64a9d574a9c7abe71998553c3a602f124d9

BIN
nitrofiles/484e4145/tmd.256 Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
0bb15b299d5dbe236f6194b5e9091eebe05264c7

View File

@ -0,0 +1 @@
4c68c41515868deb79f1a79a8e45c4248a4ed2c2

BIN
nitrofiles/484e4145/tmd.512 Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
7cf1ab87b14a325626f730579277e66ad51f7134

View File

@ -0,0 +1 @@
6c52561a047c8036f31f9d468570d50d36f7d988

BIN
nitrofiles/484e4145/tmd.768 Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
393fdcc1a46baf35fc5324928865736fa9bb1816

View File

@ -0,0 +1 @@
5a27d8d7437380252cf35786231f8adcc4cf3ff6

BIN
nitrofiles/484e414a/tmd.0 Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
f98c9648e668b8c30d1e85926626bf971cfbf749

View File

@ -0,0 +1 @@
e99b6e6e9e8e46d2a0ad820a2c8e187829b3001a

Binary file not shown.

View File

@ -0,0 +1 @@
ea8006223ad563928c0174c5792e9d823ab6bd3a

View File

@ -0,0 +1 @@
dbbebf6f8c5697e1eb1727a6a20c7a077670c30c

Binary file not shown.

View File

@ -0,0 +1 @@
9a51af752391184023f9bf2bfe33e9a17af7f977

View File

@ -0,0 +1 @@
470a6087932b7a435f879ad2ff695c4e097eae22

Binary file not shown.

View File

@ -0,0 +1 @@
fa9d59ec03d9f8b31065bf7327d3d8ba15b61e97

View File

@ -0,0 +1 @@
54264f425a499bc1ea13a6dd20e2f8c3e2c271ee

Binary file not shown.

View File

@ -0,0 +1 @@
ad741b0ef09b4a5f1f35f5737c8abd7b2edb9239

View File

@ -0,0 +1 @@
c731a5a43941ff12d6814e0fc8b4f41b0481aa21

BIN
nitrofiles/484e414a/tmd.256 Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
3b321c1e135fcbde14b9e2016c5d4b6abd19bdea

View File

@ -0,0 +1 @@
84796177e89ec2687929becfb9a83481ae93f077

BIN
nitrofiles/484e414a/tmd.512 Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
efc5c2e832373f76a0fa3e02aa9ba57c31b7b450

View File

@ -0,0 +1 @@
6a20da037dd7fcb454bdf5b3f3553f4fa37a24ce

BIN
nitrofiles/484e414a/tmd.768 Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
95898a6eeed084bc20c2d39ce6467c1351eaf4a5

View File

@ -0,0 +1 @@
ce3df01d150ef8a543b9916a7ff811f41d947813

Binary file not shown.

View File

@ -0,0 +1 @@
9ab4d682b3b1c152da66272c1be952e2b8b76824

View File

@ -0,0 +1 @@
ddb3908d8dfd29eba996caeaed79e9356c8a6d27

Binary file not shown.

View File

@ -0,0 +1 @@
049a12c7ab4d714409554e51ba4ba420f790ada4

View File

@ -0,0 +1 @@
52dc147e431a7f219096165e1d1521b1a6dba705

Binary file not shown.

View File

@ -0,0 +1 @@
9dc4d3093880a1ed965d0768f69099c160ad5c48

View File

@ -0,0 +1 @@
d3449c4cbfb0904246ad2091a30c11b40c48ef16

Binary file not shown.

View File

@ -0,0 +1 @@
68e1f16adc4a25bc8b63a741004be6b8bd098517

View File

@ -0,0 +1 @@
c4e583b568e6f52ffa9cb6a4d3f810660ba5532f

BIN
nitrofiles/484e414b/tmd.512 Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
e483dcfd3fa330dc95fe349c0fa95f32ccb6f6ec

View File

@ -0,0 +1 @@
8b94dea2b03055a5729e61a247ee70df0d9ebbff

BIN
nitrofiles/484e414b/tmd.768 Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
86ca8b148a65462b51e59b6d0f47a3fd6da1a597

View File

@ -0,0 +1 @@
8bda71fb406de03486825c27c653d8968c039fe3

Binary file not shown.

View File

@ -0,0 +1 @@
f60acf3acadb565d08719a79efaba5231f077a62

View File

@ -0,0 +1 @@
1141459540fd9df26208609a41db5eb19fce2093

Binary file not shown.

View File

@ -0,0 +1 @@
60374b640ce21b3cae134b6ee29d16eabbaaad6d

View File

@ -0,0 +1 @@
51e5befb51db74fb9fd0da19906dd5b03aaea92d

Binary file not shown.

View File

@ -0,0 +1 @@
8fb6cd27d752b90628993a4745f147738af474ed

View File

@ -0,0 +1 @@
b14f07b0325f6e08c63b52bddfba7d35285e89d7

Binary file not shown.

View File

@ -0,0 +1 @@
ad8c172b60d313a8e42a93b82a66ba9fad02e48f

Some files were not shown because too many files have changed in this diff Show More