Supports patching unlaunch with custom backgrounds

This commit is contained in:
Edoardo Lolletti 2024-04-26 19:03:23 +02:00
parent 80866c08ea
commit 94cb35df6f
12 changed files with 370 additions and 12 deletions

75
arm9/src/bgMenu.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "bgMenu.h"
#include "main.h"
#include "menu.h"
#include <nds.h>
#include <dirent.h>
#include <string>
#include <vector>
static const auto& getBackgroundList()
{
static auto bgs = []{
std::vector<std::pair<std::string,std::string>> bgs;
static const std::string bgstr{"nitro:/backgrounds/"};
auto* pdir = opendir("nitro:/backgrounds");
if(!pdir) return bgs;
dirent* pent;
while((pent = readdir(pdir))) {
if(pent->d_type == DT_DIR)
continue;
std::string name{pent->d_name};
if(!name.ends_with(".gif") && !name.ends_with(".GIF"))
continue;
bgs.emplace_back(name.substr(0, name.size() - 4), bgstr + name);
}
closedir(pdir);
return bgs;
}();
return bgs;
}
const char* backgroundMenu()
{
//top screen
clearScreen(&topScreen);
//menu
Menu* m = newMenu();
setMenuHeader(m, "BACKGROUNDS");
const auto& bgs = getBackgroundList();
for(const auto& [bgName, bgPath] : bgs)
{
addMenuItem(m, bgName.data(), nullptr, 0);
}
addMenuItem(m, "Default", nullptr, 0);
addMenuItem(m, "Cancel", nullptr, 0);
m->cursor = 0;
//bottom screen
printMenu(m);
while (!programEnd)
{
swiWaitForVBlank();
scanKeys();
if (moveCursor(m))
printMenu(m);
if (keysDown() & KEY_A)
break;
}
const char* result = nullptr;
if(static_cast<size_t>(m->cursor) < bgs.size())
result = bgs[m->cursor].second.data();
else if(static_cast<size_t>(m->cursor) == bgs.size())
result = "default";
freeMenu(m);
return result;
}

14
arm9/src/bgMenu.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef BGMENU_H
#define BGMENU_H
#ifdef __cplusplus
extern "C" {
#endif
const char* backgroundMenu();
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,3 +1,4 @@
#include "bgMenu.h"
#include "main.h" #include "main.h"
#include "menu.h" #include "menu.h"
#include "message.h" #include "message.h"
@ -17,6 +18,7 @@ static UNLAUNCH_VERSION foundUnlaunchInstallerVersion = INVALID;
static bool disableAllPatches = false; static bool disableAllPatches = false;
static bool enableSoundAndSplash = false; static bool enableSoundAndSplash = false;
static const char* splashSoundBinaryPatchPath = NULL; static const char* splashSoundBinaryPatchPath = NULL;
static const char* customBgPath = NULL;
bool charging = false; bool charging = false;
u8 batteryLevel = 0; u8 batteryLevel = 0;
@ -25,6 +27,7 @@ PrintConsole bottomScreen;
enum { enum {
MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL, MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL,
MAIN_MENU_CUSTOM_BG,
MAIN_MENU_TID_PATCHES, MAIN_MENU_TID_PATCHES,
MAIN_MENU_SOUND_SPLASH_PATCHES, MAIN_MENU_SOUND_SPLASH_PATCHES,
MAIN_MENU_SAFE_UNLAUNCH_INSTALL, MAIN_MENU_SAFE_UNLAUNCH_INSTALL,
@ -69,8 +72,9 @@ static int mainMenu(int cursor)
Menu* m = newMenu(); Menu* m = newMenu();
setMenuHeader(m, "MAIN MENU"); setMenuHeader(m, "MAIN MENU");
char uninstallStr[32], installStr[32], soundPatchesStr[64], tidPatchesStr[32]; char uninstallStr[32], installStr[32], soundPatchesStr[64], tidPatchesStr[32], customBgStr[32];
sprintf(uninstallStr, "\x1B[%02omUninstall unlaunch", unlaunchFound ? 047 : 037); sprintf(uninstallStr, "\x1B[%02omUninstall unlaunch", unlaunchFound ? 047 : 037);
sprintf(customBgStr, "\x1B[%02omCustom background", (foundUnlaunchInstallerVersion != INVALID) ? 047 : 037);
sprintf(tidPatchesStr, "\x1B[%02omDisable all patches: %s", sprintf(tidPatchesStr, "\x1B[%02omDisable all patches: %s",
(foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0) ? 047 : 037, (foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0) ? 047 : 037,
disableAllPatches ? "On" : "Off"); disableAllPatches ? "On" : "Off");
@ -79,6 +83,7 @@ static int mainMenu(int cursor)
enableSoundAndSplash ? "On" : "Off"); enableSoundAndSplash ? "On" : "Off");
sprintf(installStr, "\x1B[%02omInstall unlaunch", (foundUnlaunchInstallerVersion != INVALID && !unlaunchFound) ? 047 : 037); sprintf(installStr, "\x1B[%02omInstall unlaunch", (foundUnlaunchInstallerVersion != INVALID && !unlaunchFound) ? 047 : 037);
addMenuItem(m, uninstallStr, NULL, 0); addMenuItem(m, uninstallStr, NULL, 0);
addMenuItem(m, customBgStr, NULL, true);
addMenuItem(m, tidPatchesStr, NULL, 0); addMenuItem(m, tidPatchesStr, NULL, 0);
addMenuItem(m, soundPatchesStr, NULL, 0); addMenuItem(m, soundPatchesStr, NULL, 0);
addMenuItem(m, installStr, NULL, 0); addMenuItem(m, installStr, NULL, 0);
@ -257,6 +262,22 @@ int main(int argc, char **argv)
} }
break; break;
case MAIN_MENU_CUSTOM_BG:
if(foundUnlaunchInstallerVersion != INVALID) {
const char* customBg = backgroundMenu();
if(!customBg)
break;
if(strcmp(customBg, "default") == 0)
{
customBgPath = NULL;
}
else
{
customBgPath = customBg;
}
}
break;
case MAIN_MENU_TID_PATCHES: case MAIN_MENU_TID_PATCHES:
if(foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0) { if(foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0) {
disableAllPatches = !disableAllPatches; disableAllPatches = !disableAllPatches;
@ -279,7 +300,8 @@ int main(int argc, char **argv)
if(installUnlaunch(retailConsole, if(installUnlaunch(retailConsole,
retailLauncherTmdPresentAndToBePatched ? retailLauncherTmdPath : NULL, retailLauncherTmdPresentAndToBePatched ? retailLauncherTmdPath : NULL,
disableAllPatches, disableAllPatches,
enableSoundAndSplash ? splashSoundBinaryPatchPath : NULL)) enableSoundAndSplash ? splashSoundBinaryPatchPath : NULL,
customBgPath))
{ {
messageBox("Install successful!\n"); messageBox("Install successful!\n");
unlaunchFound = true; unlaunchFound = true;

View File

@ -77,7 +77,7 @@ void sortMenuItems(Menu* m)
qsort(m->items, m->itemCount, sizeof(Item), alphabeticalCompare); qsort(m->items, m->itemCount, sizeof(Item), alphabeticalCompare);
} }
void setMenuHeader(Menu* m, char* str) void setMenuHeader(Menu* m, const char* str)
{ {
if (!m) return; if (!m) return;
@ -87,7 +87,7 @@ void setMenuHeader(Menu* m, char* str)
return; return;
} }
char* strPtr = str; const char* strPtr = str;
if (strlen(strPtr) > 30) if (strlen(strPtr) > 30)
strPtr = str + (strlen(strPtr) - 30); strPtr = str + (strlen(strPtr) - 30);

View File

@ -30,7 +30,7 @@ void freeMenu(Menu* m);
void addMenuItem(Menu* m, char const* label, char const* value, bool directory); void addMenuItem(Menu* m, char const* label, char const* value, bool directory);
void sortMenuItems(Menu* m); void sortMenuItems(Menu* m);
void setMenuHeader(Menu* m, char* str); void setMenuHeader(Menu* m, const char* str);
void resetMenu(Menu* m); void resetMenu(Menu* m);
void clearMenu(Menu* m); void clearMenu(Menu* m);

43
arm9/src/tonccpy.h Normal file
View File

@ -0,0 +1,43 @@
//# Stuff you may not have yet.
#ifndef TONCCPY_H
#define TONCCPY_H
#ifdef __cplusplus
extern "C" {
#endif
#include <nds/ndstypes.h>
typedef unsigned int uint;
#define BIT_MASK(len) ( (1<<(len))-1 )
static inline u32 quad8(u16 x) { x |= x<<8; return x | x<<16; }
//# Declarations and inlines.
void tonccpy(void *dst, const void *src, uint size);
void __toncset(void *dst, u32 fill, uint size);
static inline void toncset(void *dst, u8 src, uint size);
static inline void toncset16(void *dst, u16 src, uint size);
static inline void toncset32(void *dst, u32 src, uint size);
//! VRAM-safe memset, byte version. Size in bytes.
static inline void toncset(void *dst, u8 src, uint size)
{ __toncset(dst, quad8(src), size); }
//! VRAM-safe memset, halfword version. Size in hwords.
static inline void toncset16(void *dst, u16 src, uint size)
{ __toncset(dst, src|src<<16, size*2); }
//! VRAM-safe memset, word version. Size in words.
static inline void toncset32(void *dst, u32 src, uint size)
{ __toncset(dst, src, size*4); }
#ifdef __cplusplus
}
#endif
#endif

136
arm9/src/tonccpy.itcm.c Normal file
View File

@ -0,0 +1,136 @@
#include "tonccpy.h"
//# tonccpy.c
//! VRAM-safe cpy.
/*! This version mimics memcpy in functionality, with
the benefit of working for VRAM as well. It is also
slightly faster than the original memcpy, but faster
implementations can be made.
\param dst Destination pointer.
\param src Source pointer.
\param size Fill-length in bytes.
\note The pointers and size need not be word-aligned.
*/
void tonccpy(void *dst, const void *src, uint size)
{
if(size==0 || dst==0 || src==0)
return;
uint count;
u16 *dst16; // hword destination
u8 *src8; // byte source
// Ideal case: copy by 4x words. Leaves tail for later.
if( ((u32)src|(u32)dst)%4==0 && size>=4)
{
u32 *src32= (u32*)src, *dst32= (u32*)dst;
count= size/4;
uint tmp= count&3;
count /= 4;
// Duff's Device, good friend!
switch(tmp) {
do { *dst32++ = *src32++;
case 3: *dst32++ = *src32++;
case 2: *dst32++ = *src32++;
case 1: *dst32++ = *src32++;
case 0: ; } while(count--);
}
// Check for tail
size &= 3;
if(size == 0)
return;
src8= (u8*)src32;
dst16= (u16*)dst32;
}
else // Unaligned.
{
uint dstOfs= (u32)dst&1;
src8= (u8*)src;
dst16= (u16*)(dst-dstOfs);
// Head: 1 byte.
if(dstOfs != 0)
{
*dst16= (*dst16 & 0xFF) | *src8++<<8;
dst16++;
if(--size==0)
return;
}
}
// Unaligned main: copy by 2x byte.
count= size/2;
while(count--)
{
*dst16++ = src8[0] | src8[1]<<8;
src8 += 2;
}
// Tail: 1 byte.
if(size&1)
*dst16= (*dst16 &~ 0xFF) | *src8;
}
//# toncset.c
//! VRAM-safe memset, internal routine.
/*! This version mimics memset in functionality, with
the benefit of working for VRAM as well. It is also
slightly faster than the original memset.
\param dst Destination pointer.
\param fill Word to fill with.
\param size Fill-length in bytes.
\note The \a dst pointer and \a size need not be
word-aligned. In the case of unaligned fills, \a fill
will be masked off to match the situation.
*/
void __toncset(void *dst, u32 fill, uint size)
{
if(size==0 || dst==0)
return;
uint left= (u32)dst&3;
u32 *dst32= (u32*)(dst-left);
u32 count, mask;
// Unaligned head.
if(left != 0)
{
// Adjust for very small stint.
if(left+size<4)
{
mask= BIT_MASK(size*8)<<(left*8);
*dst32= (*dst32 &~ mask) | (fill & mask);
return;
}
mask= BIT_MASK(left*8);
*dst32= (*dst32 & mask) | (fill&~mask);
dst32++;
size -= 4-left;
}
// Main stint.
count= size/4;
uint tmp= count&3;
count /= 4;
switch(tmp) {
do { *dst32++ = fill;
case 3: *dst32++ = fill;
case 2: *dst32++ = fill;
case 1: *dst32++ = fill;
case 0: ; } while(count--);
}
// Tail
size &= 3;
if(size)
{
mask= BIT_MASK(size*8);
*dst32= (*dst32 &~ mask) | (fill & mask);
}
}

View File

@ -1,15 +1,16 @@
#include "message.h" #include "message.h"
#include "sha1digest.h" #include "sha1digest.h"
#include "storage.h" #include "storage.h"
#include "tonccpy.h"
#include "unlaunch.h" #include "unlaunch.h"
#include <algorithm> #include <algorithm>
#include <array>
#include <nds/sha1.h> #include <nds/sha1.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
static char unlaunchInstallerBuffer[0x30000]; static char unlaunchInstallerBuffer[0x30000];
static char ogUnlaunchInstallerBuffer[0x30000];
static const char* hnaaTmdPath = "nand:/title/00030017/484e4141/content/title.tmd"; static const char* hnaaTmdPath = "nand:/title/00030017/484e4141/content/title.tmd";
static const char* hnaaBackupTmdPath = "nand:/title/00030017/484e4141/content/title.tmd.bak"; static const char* hnaaBackupTmdPath = "nand:/title/00030017/484e4141/content/title.tmd.bak";
@ -32,6 +33,12 @@ constexpr std::array knownUnlaunchHashes{
"15f4a36251d1408d71114019b2825fe8f5b4c8cc"_sha1, // v2.0 "15f4a36251d1408d71114019b2825fe8f5b4c8cc"_sha1, // v2.0
}; };
constexpr std::array gifOffsets{
std::make_pair(0x48d4, 0x8540), /* 1.8 */
std::make_pair(0x48c8, 0x8534), /* 1.9 */
std::make_pair(0x48f0, 0x855c), /* 2.0 */
};
constexpr std::array blockAllPatchesOffset{ constexpr std::array blockAllPatchesOffset{
0xae74, /* 1.9 */ 0xae74, /* 1.9 */
0xae91, /* 2.0 */ 0xae91, /* 2.0 */
@ -366,12 +373,12 @@ static bool readUnlaunchInstaller(const char* path)
} }
int toRead = unlaunchInstallerSize; int toRead = unlaunchInstallerSize;
char* buff = unlaunchInstallerBuffer; auto* buff = unlaunchInstallerBuffer;
// Pad the installer with 520 bytes, those being the size of a valid tmd // Pad the installer with 520 bytes, those being the size of a valid tmd
buff += 520; buff += 520;
size_t n = 0; size_t n = 0;
while (toRead != 0 && (n = fread(buff, sizeof(char), toRead, unlaunchInstaller)) > 0) while (toRead != 0 && (n = fread(buff, sizeof(uint8_t), toRead, unlaunchInstaller)) > 0)
{ {
toRead -= n; toRead -= n;
buff += n; buff += n;
@ -402,8 +409,64 @@ static bool verifyUnlaunchInstaller(void)
return true; return true;
} }
static bool patchUnlaunchInstaller(bool disableAllPatches, const char* splashSoundBinaryPatchPath) static bool patchCustomBackground(const char* customBackgroundPath)
{ {
if(!customBackgroundPath)
{
return true;
}
auto bgGif = fopen(customBackgroundPath, "rb");
if(!bgGif)
{
messageBox("\x1B[31mError:\x1B[33m Failed to open custom bg gif.\n");
return false;
}
auto size = getFileSize(bgGif);
if(size < 7 || size > 0x3C70)
{
messageBox("\x1B[31mError:\x1B[33m Invalid gif file.\n");
fclose(bgGif);
return false;
}
u16 gifWidth;
u16 gifHeight;
if((fseek(bgGif, 6, SEEK_SET) != 0) || (fread(&gifWidth, 1, sizeof(u16), bgGif) != sizeof(u16)) || (fread(&gifHeight, 1, sizeof(u16), bgGif) != sizeof(u16)))
{
messageBox("\x1B[31mError:\x1B[33m Failed to parse gif file.\n");
fclose(bgGif);
return false;
}
if (gifWidth != 256 || gifHeight != 192) {
messageBox("\x1B[31mError:\x1B[33m Gif file has invalid dimensions.\n");
fclose(bgGif);
return false;
}
const u32 gifSignatureStart = 0x38464947;
const u32 gifSignatureEnd = 0x3B000044;
auto [gifOffsetStart, gifOffsetEnd] = gifOffsets[installerVersion];
auto* gifStart = reinterpret_cast<uint32_t*>((unlaunchInstallerBuffer + 520) + gifOffsetStart);
auto* gifEnd = reinterpret_cast<uint32_t*>((unlaunchInstallerBuffer + 520) + gifOffsetEnd);
if(*gifStart != gifSignatureStart || *gifEnd != gifSignatureEnd)
{
messageBox("\x1B[31mError:\x1B[33m Gif offsets not matching.\n");
fclose(bgGif);
return false;
}
fseek(bgGif, 0, SEEK_SET);
//read the whole file, could be less than 0x3C70, but unlaunch should then just ignore the leftover data
fread(gifStart, 1, 0x3C70, bgGif);
return true;
}
static bool patchUnlaunchInstaller(bool disableAllPatches, const char* splashSoundBinaryPatchPath, const char* customBackgroundPath)
{
tonccpy(unlaunchInstallerBuffer, ogUnlaunchInstallerBuffer, sizeof(unlaunchInstallerBuffer));
if(disableAllPatches) if(disableAllPatches)
{ {
if(installerVersion == v1_8) if(installerVersion == v1_8)
@ -450,6 +513,10 @@ static bool patchUnlaunchInstaller(bool disableAllPatches, const char* splashSou
} }
fclose(patch); fclose(patch);
} }
if(!patchCustomBackground(customBackgroundPath))
{
return false;
}
return true; return true;
} }
@ -457,14 +524,15 @@ UNLAUNCH_VERSION loadUnlaunchInstaller(const char* path)
{ {
if(readUnlaunchInstaller(path) && verifyUnlaunchInstaller()) if(readUnlaunchInstaller(path) && verifyUnlaunchInstaller())
{ {
tonccpy(ogUnlaunchInstallerBuffer, unlaunchInstallerBuffer, sizeof(unlaunchInstallerBuffer));
return installerVersion; return installerVersion;
} }
return INVALID; return INVALID;
} }
bool installUnlaunch(bool retailConsole, const char* retailLauncherTmdPath, bool disableAllPatches, const char* splashSoundBinaryPatchPath) bool installUnlaunch(bool retailConsole, const char* retailLauncherTmdPath, bool disableAllPatches, const char* splashSoundBinaryPatchPath, const char* customBackgroundPath)
{ {
if (installerVersion == INVALID || !patchUnlaunchInstaller(disableAllPatches, splashSoundBinaryPatchPath)) if (installerVersion == INVALID || !patchUnlaunchInstaller(disableAllPatches, splashSoundBinaryPatchPath, customBackgroundPath))
return false; return false;
// Treat protos differently // Treat protos differently

View File

@ -14,7 +14,7 @@ typedef enum UNLAUNCH_VERSION {
} UNLAUNCH_VERSION; } UNLAUNCH_VERSION;
bool uninstallUnlaunch(bool notProto, bool hasHNAABackup, const char* retailLauncherTmdPath); bool uninstallUnlaunch(bool notProto, bool hasHNAABackup, const char* retailLauncherTmdPath);
bool installUnlaunch(bool retailConsole, const char* retailLauncherTmdPath, bool disableAllPatches, const char* splashSoundBinaryPatchPath); bool installUnlaunch(bool retailConsole, const char* retailLauncherTmdPath, bool disableAllPatches, const char* splashSoundBinaryPatchPath, const char* customBackgroundPath);
bool isLauncherTmdPatched(const char* path); bool isLauncherTmdPatched(const char* path);

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB