mirror of
https://github.com/rvtr/unlaunch-installer_dev.git
synced 2026-01-26 13:43:08 -05:00
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:
parent
f7fb51c244
commit
cae02f6607
3
Makefile
3
Makefile
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
27
arm9/src/consoleInfo.h
Normal 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
|
||||||
561
arm9/src/main.c
561
arm9/src/main.c
@ -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
705
arm9/src/main.cpp
Normal 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();
|
||||||
|
}
|
||||||
@ -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,20 +53,6 @@ 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* {
|
||||||
@ -94,40 +82,29 @@ 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;
|
||||||
|
fwrite(&c, 1, 1, launcherTmd);
|
||||||
|
fflush(launcherTmd);
|
||||||
|
} else if(!info.tmdGood || info.tmdInvalid) {
|
||||||
|
// The tmd isn't good, it either has the wrong size, or the hash didn't match
|
||||||
|
// and it wasn't patched with the new method
|
||||||
|
// Install the hnaa backup if not found and then truncate the tmd to 520b
|
||||||
|
// before restoring it
|
||||||
|
if(!info.UnlaunchHNAAtmdFound && !removeHNAABackup)
|
||||||
{
|
{
|
||||||
auto choiceString = [&]{
|
auto choiceString = [&]{
|
||||||
if(installerVersion != INVALID)
|
if(installerVersion != INVALID)
|
||||||
@ -146,7 +123,6 @@ static bool restoreMainTmd(const char* path, bool hasHNAABackup, bool removeHNAA
|
|||||||
}();
|
}();
|
||||||
if(choiceBox(choiceString) == NO)
|
if(choiceBox(choiceString) == NO)
|
||||||
{
|
{
|
||||||
fclose(launcherTmd);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(installerVersion != INVALID)
|
if(installerVersion != INVALID)
|
||||||
@ -157,28 +133,33 @@ static bool restoreMainTmd(const char* path, bool hasHNAABackup, bool removeHNAA
|
|||||||
"be copmleted.\n"
|
"be copmleted.\n"
|
||||||
"Proceed anyways?") == NO)
|
"Proceed anyways?") == NO)
|
||||||
{
|
{
|
||||||
fclose(launcherTmd);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(removeHNAABackup && hasHNAABackup)
|
|
||||||
{
|
|
||||||
removeHnaaLauncher();
|
|
||||||
}
|
|
||||||
if (ftruncate(fileno(launcherTmd), 520) != 0) {
|
if (ftruncate(fileno(launcherTmd), 520) != 0) {
|
||||||
messageBox("\x1B[31mError:\x1B[33m Failed to remove unlaunch\n");
|
messageBox("\x1B[31mError:\x1B[33m Failed to remove unlaunch\n");
|
||||||
fclose(launcherTmd);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
fclose(launcherTmd);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
messageBox("\x1B[31mError:\x1B[33m Unlaunch was installed with an\nunknown method\naborting\n");
|
|
||||||
fclose(launcherTmd);
|
Sha1Digest digest;
|
||||||
|
calculateFileSha1(launcherTmd, &digest);
|
||||||
|
|
||||||
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if(removeHNAABackup && info.UnlaunchHNAAtmdFound)
|
||||||
|
{
|
||||||
|
return removeHnaaLauncher();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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;
|
return false;
|
||||||
}
|
}
|
||||||
if (!restoreMainTmd(retailLauncherTmdPath, hasHNAABackup, removeHNAABackup))
|
if(!toggleFileReadOnly(info.launcherAppPath.data(), false))
|
||||||
|
{
|
||||||
|
messageBox(std::format("\x1B[31mError:\x1B[33m Failed to make {} writable\n", info.launcherAppPath).data());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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,19 +307,19 @@ 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
|
// Set tmd as writable in case unlaunch was already installed through the old method
|
||||||
if(!toggleFileReadOnly(retailLauncherTmdPath, false) || !patchMainTmd(retailLauncherTmdPath))
|
if(!toggleFileReadOnly(info.launcherTmdPath.data(), false) || !patchMainTmd(info.launcherTmdPath.data()))
|
||||||
{
|
{
|
||||||
removeHnaaLauncher();
|
removeHnaaLauncher();
|
||||||
return false;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
BIN
nitrofiles/484e4143/tmd.1024
Normal file
BIN
nitrofiles/484e4143/tmd.1024
Normal file
Binary file not shown.
1
nitrofiles/484e4143/tmd.1024.patch.sha1
Normal file
1
nitrofiles/484e4143/tmd.1024.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
d20ef5520ef3df8bc418f46075cc5e6865ed4b6e
|
||||||
1
nitrofiles/484e4143/tmd.1024.sha1
Normal file
1
nitrofiles/484e4143/tmd.1024.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
88076c8d9e8330e43e32bba7c24fb2970d16241e
|
||||||
BIN
nitrofiles/484e4143/tmd.1280
Normal file
BIN
nitrofiles/484e4143/tmd.1280
Normal file
Binary file not shown.
1
nitrofiles/484e4143/tmd.1280.patch.sha1
Normal file
1
nitrofiles/484e4143/tmd.1280.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
7fc30f65d75ca08ff6c356527a8cc4f62669a69e
|
||||||
1
nitrofiles/484e4143/tmd.1280.sha1
Normal file
1
nitrofiles/484e4143/tmd.1280.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
58e0db23ee9b0cae9d7e83fd39b1781a7eebf3bb
|
||||||
BIN
nitrofiles/484e4143/tmd.1536
Normal file
BIN
nitrofiles/484e4143/tmd.1536
Normal file
Binary file not shown.
1
nitrofiles/484e4143/tmd.1536.patch.sha1
Normal file
1
nitrofiles/484e4143/tmd.1536.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
dab8a54b21f6c76062b0ad2a2e195a09c27960fd
|
||||||
1
nitrofiles/484e4143/tmd.1536.sha1
Normal file
1
nitrofiles/484e4143/tmd.1536.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
7c57ec724652b98923b2111150062657119644c5
|
||||||
BIN
nitrofiles/484e4143/tmd.1792
Normal file
BIN
nitrofiles/484e4143/tmd.1792
Normal file
Binary file not shown.
1
nitrofiles/484e4143/tmd.1792.patch.sha1
Normal file
1
nitrofiles/484e4143/tmd.1792.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
0358cb04f28d038cf6337a9d05ae58b3ad86af59
|
||||||
1
nitrofiles/484e4143/tmd.1792.sha1
Normal file
1
nitrofiles/484e4143/tmd.1792.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
e0a43818affb9c2849230abba12bc59fae48aa69
|
||||||
BIN
nitrofiles/484e4143/tmd.512
Normal file
BIN
nitrofiles/484e4143/tmd.512
Normal file
Binary file not shown.
1
nitrofiles/484e4143/tmd.512.patch.sha1
Normal file
1
nitrofiles/484e4143/tmd.512.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
4e350d55f0b134748ddaf1ff1eef99de4c7dfa85
|
||||||
1
nitrofiles/484e4143/tmd.512.sha1
Normal file
1
nitrofiles/484e4143/tmd.512.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
76727f47fa55a06d0b684745c5e6cdd5f596c4e5
|
||||||
BIN
nitrofiles/484e4143/tmd.768
Normal file
BIN
nitrofiles/484e4143/tmd.768
Normal file
Binary file not shown.
1
nitrofiles/484e4143/tmd.768.patch.sha1
Normal file
1
nitrofiles/484e4143/tmd.768.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
b1d18a727dae139451a6e2ca4b89dd22253d4639
|
||||||
1
nitrofiles/484e4143/tmd.768.sha1
Normal file
1
nitrofiles/484e4143/tmd.768.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
ebd7cff8b0d3ccca8579872b29dc20cc3a0827d1
|
||||||
BIN
nitrofiles/484e4145/tmd.1024
Normal file
BIN
nitrofiles/484e4145/tmd.1024
Normal file
Binary file not shown.
1
nitrofiles/484e4145/tmd.1024.patch.sha1
Normal file
1
nitrofiles/484e4145/tmd.1024.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
a91f95d68dccd359987ef7e32ec2221148153da6
|
||||||
1
nitrofiles/484e4145/tmd.1024.sha1
Normal file
1
nitrofiles/484e4145/tmd.1024.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
7795346d34093c0d73db587f6c5fd8510b947ec7
|
||||||
BIN
nitrofiles/484e4145/tmd.1280
Normal file
BIN
nitrofiles/484e4145/tmd.1280
Normal file
Binary file not shown.
1
nitrofiles/484e4145/tmd.1280.patch.sha1
Normal file
1
nitrofiles/484e4145/tmd.1280.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
4c482027655615abe80ed32a26f064fd97caf80c
|
||||||
1
nitrofiles/484e4145/tmd.1280.sha1
Normal file
1
nitrofiles/484e4145/tmd.1280.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
978f1a3b26a4f983a7e305d1a89c73d0890e6683
|
||||||
BIN
nitrofiles/484e4145/tmd.1536
Normal file
BIN
nitrofiles/484e4145/tmd.1536
Normal file
Binary file not shown.
1
nitrofiles/484e4145/tmd.1536.patch.sha1
Normal file
1
nitrofiles/484e4145/tmd.1536.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
c758f170bc58b6a2bc60cd521456b76a0304fe2c
|
||||||
1
nitrofiles/484e4145/tmd.1536.sha1
Normal file
1
nitrofiles/484e4145/tmd.1536.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
ca573c0014319ce6a5e3ead6197d07c84783f21d
|
||||||
BIN
nitrofiles/484e4145/tmd.1792
Normal file
BIN
nitrofiles/484e4145/tmd.1792
Normal file
Binary file not shown.
1
nitrofiles/484e4145/tmd.1792.patch.sha1
Normal file
1
nitrofiles/484e4145/tmd.1792.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
4702ac21227a9a1e2d46b6c336e8dc99b1ec25bf
|
||||||
1
nitrofiles/484e4145/tmd.1792.sha1
Normal file
1
nitrofiles/484e4145/tmd.1792.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
a297f64a9d574a9c7abe71998553c3a602f124d9
|
||||||
BIN
nitrofiles/484e4145/tmd.256
Normal file
BIN
nitrofiles/484e4145/tmd.256
Normal file
Binary file not shown.
1
nitrofiles/484e4145/tmd.256.patch.sha1
Normal file
1
nitrofiles/484e4145/tmd.256.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
0bb15b299d5dbe236f6194b5e9091eebe05264c7
|
||||||
1
nitrofiles/484e4145/tmd.256.sha1
Normal file
1
nitrofiles/484e4145/tmd.256.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
4c68c41515868deb79f1a79a8e45c4248a4ed2c2
|
||||||
BIN
nitrofiles/484e4145/tmd.512
Normal file
BIN
nitrofiles/484e4145/tmd.512
Normal file
Binary file not shown.
1
nitrofiles/484e4145/tmd.512.patch.sha1
Normal file
1
nitrofiles/484e4145/tmd.512.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
7cf1ab87b14a325626f730579277e66ad51f7134
|
||||||
1
nitrofiles/484e4145/tmd.512.sha1
Normal file
1
nitrofiles/484e4145/tmd.512.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
6c52561a047c8036f31f9d468570d50d36f7d988
|
||||||
BIN
nitrofiles/484e4145/tmd.768
Normal file
BIN
nitrofiles/484e4145/tmd.768
Normal file
Binary file not shown.
1
nitrofiles/484e4145/tmd.768.patch.sha1
Normal file
1
nitrofiles/484e4145/tmd.768.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
393fdcc1a46baf35fc5324928865736fa9bb1816
|
||||||
1
nitrofiles/484e4145/tmd.768.sha1
Normal file
1
nitrofiles/484e4145/tmd.768.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
5a27d8d7437380252cf35786231f8adcc4cf3ff6
|
||||||
BIN
nitrofiles/484e414a/tmd.0
Normal file
BIN
nitrofiles/484e414a/tmd.0
Normal file
Binary file not shown.
1
nitrofiles/484e414a/tmd.0.patch.sha1
Normal file
1
nitrofiles/484e414a/tmd.0.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
f98c9648e668b8c30d1e85926626bf971cfbf749
|
||||||
1
nitrofiles/484e414a/tmd.0.sha1
Normal file
1
nitrofiles/484e414a/tmd.0.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
e99b6e6e9e8e46d2a0ad820a2c8e187829b3001a
|
||||||
BIN
nitrofiles/484e414a/tmd.1024
Normal file
BIN
nitrofiles/484e414a/tmd.1024
Normal file
Binary file not shown.
1
nitrofiles/484e414a/tmd.1024.patch.sha1
Normal file
1
nitrofiles/484e414a/tmd.1024.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
ea8006223ad563928c0174c5792e9d823ab6bd3a
|
||||||
1
nitrofiles/484e414a/tmd.1024.sha1
Normal file
1
nitrofiles/484e414a/tmd.1024.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
dbbebf6f8c5697e1eb1727a6a20c7a077670c30c
|
||||||
BIN
nitrofiles/484e414a/tmd.1280
Normal file
BIN
nitrofiles/484e414a/tmd.1280
Normal file
Binary file not shown.
1
nitrofiles/484e414a/tmd.1280.patch.sha1
Normal file
1
nitrofiles/484e414a/tmd.1280.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
9a51af752391184023f9bf2bfe33e9a17af7f977
|
||||||
1
nitrofiles/484e414a/tmd.1280.sha1
Normal file
1
nitrofiles/484e414a/tmd.1280.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
470a6087932b7a435f879ad2ff695c4e097eae22
|
||||||
BIN
nitrofiles/484e414a/tmd.1536
Normal file
BIN
nitrofiles/484e414a/tmd.1536
Normal file
Binary file not shown.
1
nitrofiles/484e414a/tmd.1536.patch.sha1
Normal file
1
nitrofiles/484e414a/tmd.1536.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
fa9d59ec03d9f8b31065bf7327d3d8ba15b61e97
|
||||||
1
nitrofiles/484e414a/tmd.1536.sha1
Normal file
1
nitrofiles/484e414a/tmd.1536.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
54264f425a499bc1ea13a6dd20e2f8c3e2c271ee
|
||||||
BIN
nitrofiles/484e414a/tmd.1792
Normal file
BIN
nitrofiles/484e414a/tmd.1792
Normal file
Binary file not shown.
1
nitrofiles/484e414a/tmd.1792.patch.sha1
Normal file
1
nitrofiles/484e414a/tmd.1792.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
ad741b0ef09b4a5f1f35f5737c8abd7b2edb9239
|
||||||
1
nitrofiles/484e414a/tmd.1792.sha1
Normal file
1
nitrofiles/484e414a/tmd.1792.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
c731a5a43941ff12d6814e0fc8b4f41b0481aa21
|
||||||
BIN
nitrofiles/484e414a/tmd.256
Normal file
BIN
nitrofiles/484e414a/tmd.256
Normal file
Binary file not shown.
1
nitrofiles/484e414a/tmd.256.patch.sha1
Normal file
1
nitrofiles/484e414a/tmd.256.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
3b321c1e135fcbde14b9e2016c5d4b6abd19bdea
|
||||||
1
nitrofiles/484e414a/tmd.256.sha1
Normal file
1
nitrofiles/484e414a/tmd.256.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
84796177e89ec2687929becfb9a83481ae93f077
|
||||||
BIN
nitrofiles/484e414a/tmd.512
Normal file
BIN
nitrofiles/484e414a/tmd.512
Normal file
Binary file not shown.
1
nitrofiles/484e414a/tmd.512.patch.sha1
Normal file
1
nitrofiles/484e414a/tmd.512.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
efc5c2e832373f76a0fa3e02aa9ba57c31b7b450
|
||||||
1
nitrofiles/484e414a/tmd.512.sha1
Normal file
1
nitrofiles/484e414a/tmd.512.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
6a20da037dd7fcb454bdf5b3f3553f4fa37a24ce
|
||||||
BIN
nitrofiles/484e414a/tmd.768
Normal file
BIN
nitrofiles/484e414a/tmd.768
Normal file
Binary file not shown.
1
nitrofiles/484e414a/tmd.768.patch.sha1
Normal file
1
nitrofiles/484e414a/tmd.768.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
95898a6eeed084bc20c2d39ce6467c1351eaf4a5
|
||||||
1
nitrofiles/484e414a/tmd.768.sha1
Normal file
1
nitrofiles/484e414a/tmd.768.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
ce3df01d150ef8a543b9916a7ff811f41d947813
|
||||||
BIN
nitrofiles/484e414b/tmd.1024
Normal file
BIN
nitrofiles/484e414b/tmd.1024
Normal file
Binary file not shown.
1
nitrofiles/484e414b/tmd.1024.patch.sha1
Normal file
1
nitrofiles/484e414b/tmd.1024.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
9ab4d682b3b1c152da66272c1be952e2b8b76824
|
||||||
1
nitrofiles/484e414b/tmd.1024.sha1
Normal file
1
nitrofiles/484e414b/tmd.1024.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
ddb3908d8dfd29eba996caeaed79e9356c8a6d27
|
||||||
BIN
nitrofiles/484e414b/tmd.1280
Normal file
BIN
nitrofiles/484e414b/tmd.1280
Normal file
Binary file not shown.
1
nitrofiles/484e414b/tmd.1280.patch.sha1
Normal file
1
nitrofiles/484e414b/tmd.1280.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
049a12c7ab4d714409554e51ba4ba420f790ada4
|
||||||
1
nitrofiles/484e414b/tmd.1280.sha1
Normal file
1
nitrofiles/484e414b/tmd.1280.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
52dc147e431a7f219096165e1d1521b1a6dba705
|
||||||
BIN
nitrofiles/484e414b/tmd.1536
Normal file
BIN
nitrofiles/484e414b/tmd.1536
Normal file
Binary file not shown.
1
nitrofiles/484e414b/tmd.1536.patch.sha1
Normal file
1
nitrofiles/484e414b/tmd.1536.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
9dc4d3093880a1ed965d0768f69099c160ad5c48
|
||||||
1
nitrofiles/484e414b/tmd.1536.sha1
Normal file
1
nitrofiles/484e414b/tmd.1536.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
d3449c4cbfb0904246ad2091a30c11b40c48ef16
|
||||||
BIN
nitrofiles/484e414b/tmd.1792
Normal file
BIN
nitrofiles/484e414b/tmd.1792
Normal file
Binary file not shown.
1
nitrofiles/484e414b/tmd.1792.patch.sha1
Normal file
1
nitrofiles/484e414b/tmd.1792.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
68e1f16adc4a25bc8b63a741004be6b8bd098517
|
||||||
1
nitrofiles/484e414b/tmd.1792.sha1
Normal file
1
nitrofiles/484e414b/tmd.1792.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
c4e583b568e6f52ffa9cb6a4d3f810660ba5532f
|
||||||
BIN
nitrofiles/484e414b/tmd.512
Normal file
BIN
nitrofiles/484e414b/tmd.512
Normal file
Binary file not shown.
1
nitrofiles/484e414b/tmd.512.patch.sha1
Normal file
1
nitrofiles/484e414b/tmd.512.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
e483dcfd3fa330dc95fe349c0fa95f32ccb6f6ec
|
||||||
1
nitrofiles/484e414b/tmd.512.sha1
Normal file
1
nitrofiles/484e414b/tmd.512.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
8b94dea2b03055a5729e61a247ee70df0d9ebbff
|
||||||
BIN
nitrofiles/484e414b/tmd.768
Normal file
BIN
nitrofiles/484e414b/tmd.768
Normal file
Binary file not shown.
1
nitrofiles/484e414b/tmd.768.patch.sha1
Normal file
1
nitrofiles/484e414b/tmd.768.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
86ca8b148a65462b51e59b6d0f47a3fd6da1a597
|
||||||
1
nitrofiles/484e414b/tmd.768.sha1
Normal file
1
nitrofiles/484e414b/tmd.768.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
8bda71fb406de03486825c27c653d8968c039fe3
|
||||||
BIN
nitrofiles/484e4150/tmd.1024
Normal file
BIN
nitrofiles/484e4150/tmd.1024
Normal file
Binary file not shown.
1
nitrofiles/484e4150/tmd.1024.patch.sha1
Normal file
1
nitrofiles/484e4150/tmd.1024.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
f60acf3acadb565d08719a79efaba5231f077a62
|
||||||
1
nitrofiles/484e4150/tmd.1024.sha1
Normal file
1
nitrofiles/484e4150/tmd.1024.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
1141459540fd9df26208609a41db5eb19fce2093
|
||||||
BIN
nitrofiles/484e4150/tmd.1280
Normal file
BIN
nitrofiles/484e4150/tmd.1280
Normal file
Binary file not shown.
1
nitrofiles/484e4150/tmd.1280.patch.sha1
Normal file
1
nitrofiles/484e4150/tmd.1280.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
60374b640ce21b3cae134b6ee29d16eabbaaad6d
|
||||||
1
nitrofiles/484e4150/tmd.1280.sha1
Normal file
1
nitrofiles/484e4150/tmd.1280.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
51e5befb51db74fb9fd0da19906dd5b03aaea92d
|
||||||
BIN
nitrofiles/484e4150/tmd.1536
Normal file
BIN
nitrofiles/484e4150/tmd.1536
Normal file
Binary file not shown.
1
nitrofiles/484e4150/tmd.1536.patch.sha1
Normal file
1
nitrofiles/484e4150/tmd.1536.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
8fb6cd27d752b90628993a4745f147738af474ed
|
||||||
1
nitrofiles/484e4150/tmd.1536.sha1
Normal file
1
nitrofiles/484e4150/tmd.1536.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
b14f07b0325f6e08c63b52bddfba7d35285e89d7
|
||||||
BIN
nitrofiles/484e4150/tmd.1792
Normal file
BIN
nitrofiles/484e4150/tmd.1792
Normal file
Binary file not shown.
1
nitrofiles/484e4150/tmd.1792.patch.sha1
Normal file
1
nitrofiles/484e4150/tmd.1792.patch.sha1
Normal file
@ -0,0 +1 @@
|
|||||||
|
ad8c172b60d313a8e42a93b82a66ba9fad02e48f
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user