mirror of
https://github.com/rvtr/unlaunch-installer_dev.git
synced 2026-01-26 13:43:08 -05:00
Supports patching unlaunch with custom backgrounds
This commit is contained in:
parent
80866c08ea
commit
94cb35df6f
75
arm9/src/bgMenu.cpp
Normal file
75
arm9/src/bgMenu.cpp
Normal 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
14
arm9/src/bgMenu.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef BGMENU_H
|
||||
#define BGMENU_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
const char* backgroundMenu();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@ -1,3 +1,4 @@
|
||||
#include "bgMenu.h"
|
||||
#include "main.h"
|
||||
#include "menu.h"
|
||||
#include "message.h"
|
||||
@ -17,6 +18,7 @@ static UNLAUNCH_VERSION foundUnlaunchInstallerVersion = INVALID;
|
||||
static bool disableAllPatches = false;
|
||||
static bool enableSoundAndSplash = false;
|
||||
static const char* splashSoundBinaryPatchPath = NULL;
|
||||
static const char* customBgPath = NULL;
|
||||
bool charging = false;
|
||||
u8 batteryLevel = 0;
|
||||
|
||||
@ -25,6 +27,7 @@ PrintConsole bottomScreen;
|
||||
|
||||
enum {
|
||||
MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL,
|
||||
MAIN_MENU_CUSTOM_BG,
|
||||
MAIN_MENU_TID_PATCHES,
|
||||
MAIN_MENU_SOUND_SPLASH_PATCHES,
|
||||
MAIN_MENU_SAFE_UNLAUNCH_INSTALL,
|
||||
@ -69,8 +72,9 @@ static int mainMenu(int cursor)
|
||||
Menu* m = newMenu();
|
||||
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(customBgStr, "\x1B[%02omCustom background", (foundUnlaunchInstallerVersion != INVALID) ? 047 : 037);
|
||||
sprintf(tidPatchesStr, "\x1B[%02omDisable all patches: %s",
|
||||
(foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0) ? 047 : 037,
|
||||
disableAllPatches ? "On" : "Off");
|
||||
@ -79,6 +83,7 @@ static int mainMenu(int cursor)
|
||||
enableSoundAndSplash ? "On" : "Off");
|
||||
sprintf(installStr, "\x1B[%02omInstall unlaunch", (foundUnlaunchInstallerVersion != INVALID && !unlaunchFound) ? 047 : 037);
|
||||
addMenuItem(m, uninstallStr, NULL, 0);
|
||||
addMenuItem(m, customBgStr, NULL, true);
|
||||
addMenuItem(m, tidPatchesStr, NULL, 0);
|
||||
addMenuItem(m, soundPatchesStr, NULL, 0);
|
||||
addMenuItem(m, installStr, NULL, 0);
|
||||
@ -257,6 +262,22 @@ int main(int argc, char **argv)
|
||||
}
|
||||
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:
|
||||
if(foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0) {
|
||||
disableAllPatches = !disableAllPatches;
|
||||
@ -279,7 +300,8 @@ int main(int argc, char **argv)
|
||||
if(installUnlaunch(retailConsole,
|
||||
retailLauncherTmdPresentAndToBePatched ? retailLauncherTmdPath : NULL,
|
||||
disableAllPatches,
|
||||
enableSoundAndSplash ? splashSoundBinaryPatchPath : NULL))
|
||||
enableSoundAndSplash ? splashSoundBinaryPatchPath : NULL,
|
||||
customBgPath))
|
||||
{
|
||||
messageBox("Install successful!\n");
|
||||
unlaunchFound = true;
|
||||
|
||||
@ -77,7 +77,7 @@ void sortMenuItems(Menu* m)
|
||||
qsort(m->items, m->itemCount, sizeof(Item), alphabeticalCompare);
|
||||
}
|
||||
|
||||
void setMenuHeader(Menu* m, char* str)
|
||||
void setMenuHeader(Menu* m, const char* str)
|
||||
{
|
||||
if (!m) return;
|
||||
|
||||
@ -87,7 +87,7 @@ void setMenuHeader(Menu* m, char* str)
|
||||
return;
|
||||
}
|
||||
|
||||
char* strPtr = str;
|
||||
const char* strPtr = str;
|
||||
|
||||
if (strlen(strPtr) > 30)
|
||||
strPtr = str + (strlen(strPtr) - 30);
|
||||
|
||||
@ -30,7 +30,7 @@ void freeMenu(Menu* m);
|
||||
|
||||
void addMenuItem(Menu* m, char const* label, char const* value, bool directory);
|
||||
void sortMenuItems(Menu* m);
|
||||
void setMenuHeader(Menu* m, char* str);
|
||||
void setMenuHeader(Menu* m, const char* str);
|
||||
|
||||
void resetMenu(Menu* m);
|
||||
void clearMenu(Menu* m);
|
||||
|
||||
43
arm9/src/tonccpy.h
Normal file
43
arm9/src/tonccpy.h
Normal 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
136
arm9/src/tonccpy.itcm.c
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,16 @@
|
||||
#include "message.h"
|
||||
#include "sha1digest.h"
|
||||
#include "storage.h"
|
||||
#include "tonccpy.h"
|
||||
#include "unlaunch.h"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <nds/sha1.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static char unlaunchInstallerBuffer[0x30000];
|
||||
static char ogUnlaunchInstallerBuffer[0x30000];
|
||||
static const char* hnaaTmdPath = "nand:/title/00030017/484e4141/content/title.tmd";
|
||||
static const char* hnaaBackupTmdPath = "nand:/title/00030017/484e4141/content/title.tmd.bak";
|
||||
|
||||
@ -32,6 +33,12 @@ constexpr std::array knownUnlaunchHashes{
|
||||
"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{
|
||||
0xae74, /* 1.9 */
|
||||
0xae91, /* 2.0 */
|
||||
@ -366,12 +373,12 @@ static bool readUnlaunchInstaller(const char* path)
|
||||
}
|
||||
|
||||
int toRead = unlaunchInstallerSize;
|
||||
char* buff = unlaunchInstallerBuffer;
|
||||
auto* buff = unlaunchInstallerBuffer;
|
||||
// Pad the installer with 520 bytes, those being the size of a valid tmd
|
||||
buff += 520;
|
||||
|
||||
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;
|
||||
buff += n;
|
||||
@ -402,8 +409,64 @@ static bool verifyUnlaunchInstaller(void)
|
||||
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(installerVersion == v1_8)
|
||||
@ -450,6 +513,10 @@ static bool patchUnlaunchInstaller(bool disableAllPatches, const char* splashSou
|
||||
}
|
||||
fclose(patch);
|
||||
}
|
||||
if(!patchCustomBackground(customBackgroundPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -457,14 +524,15 @@ UNLAUNCH_VERSION loadUnlaunchInstaller(const char* path)
|
||||
{
|
||||
if(readUnlaunchInstaller(path) && verifyUnlaunchInstaller())
|
||||
{
|
||||
tonccpy(ogUnlaunchInstallerBuffer, unlaunchInstallerBuffer, sizeof(unlaunchInstallerBuffer));
|
||||
return installerVersion;
|
||||
}
|
||||
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;
|
||||
|
||||
// Treat protos differently
|
||||
|
||||
@ -14,7 +14,7 @@ typedef enum UNLAUNCH_VERSION {
|
||||
} UNLAUNCH_VERSION;
|
||||
|
||||
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);
|
||||
|
||||
|
||||
BIN
nitrofiles/backgrounds/black.gif
Normal file
BIN
nitrofiles/backgrounds/black.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 351 B |
BIN
nitrofiles/backgrounds/desert.gif
Normal file
BIN
nitrofiles/backgrounds/desert.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
nitrofiles/backgrounds/desert_gray.gif
Normal file
BIN
nitrofiles/backgrounds/desert_gray.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Loading…
Reference in New Issue
Block a user