diff --git a/arm9/Makefile b/arm9/Makefile index 16fba63..7110017 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -31,7 +31,7 @@ CFLAGS := -g -Wall -O2\ $(ARCH) CFLAGS += $(INCLUDE) -DARM9 -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++23 ASFLAGS := -g $(ARCH) -march=armv5te -mtune=arm946e-s diff --git a/arm9/src/main.c b/arm9/src/main.c index 8bcac47..1506e9f 100644 --- a/arm9/src/main.c +++ b/arm9/src/main.c @@ -56,7 +56,7 @@ static int mainMenu(int cursor) "\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;0HJeff - 2018-2019"); + iprintf("\x1b[21;0HJeff - 2018-2019"); iprintf("\x1b[22;0HPk11 - 2022-2023"); iprintf("\x1b[23;0Hedo9300 - 2024"); @@ -68,7 +68,6 @@ static int mainMenu(int cursor) sprintf(uninstallStr, "\x1B[%02omUninstall unlaunch", unlaunchFound ? 047 : 037); sprintf(installStr, "\x1B[%02omInstall unlaunch", unlaunchInstallerFound ? 047 : 037); addMenuItem(m, uninstallStr, NULL, 0); - addMenuItem(m, uninstallStr, NULL, 0); addMenuItem(m, installStr, NULL, 0); addMenuItem(m, "\x1B[47mExit", NULL, 0); @@ -139,7 +138,7 @@ int main(int argc, char **argv) return 0; } - unlaunchInstallerFound = fileExists("sd:/unlaunch.dsi"); + unlaunchInstallerFound = loadUnlaunchInstaller("sd:/unlaunch.dsi"); if (!unlaunchInstallerFound) { messageBox("\x1B[41mWARNING:\x1B[47m unlaunch.dsi was not found in\n" diff --git a/arm9/src/main.h b/arm9/src/main.h index 99a0799..974ce78 100644 --- a/arm9/src/main.h +++ b/arm9/src/main.h @@ -5,6 +5,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + extern volatile bool programEnd; extern bool charging; extern u8 batteryLevel; @@ -14,4 +18,8 @@ extern PrintConsole bottomScreen; void clearScreen(PrintConsole* screen); +#ifdef __cplusplus +} +#endif + #endif \ No newline at end of file diff --git a/arm9/src/menu.h b/arm9/src/menu.h index ea0ebc5..2942394 100644 --- a/arm9/src/menu.h +++ b/arm9/src/menu.h @@ -3,6 +3,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + #define ITEMS_PER_PAGE 20 typedef struct { @@ -34,4 +38,8 @@ void printMenu(Menu* m); bool moveCursor(Menu* m); +#ifdef __cplusplus +} +#endif + #endif \ No newline at end of file diff --git a/arm9/src/message.c b/arm9/src/message.c index 83fec38..830a1c5 100644 --- a/arm9/src/message.c +++ b/arm9/src/message.c @@ -13,7 +13,7 @@ void keyWait(u32 key) } } -bool choiceBox(char* message) +bool choiceBox(const char* message) { const int choiceRow = 10; int cursor = 0; @@ -53,7 +53,7 @@ bool choiceBox(char* message) return (cursor == 0)? YES: NO; } -bool choicePrint(char* message) +bool choicePrint(const char* message) { bool choice = NO; @@ -87,7 +87,7 @@ bool choicePrint(char* message) const static u16 keys[] = {KEY_UP, KEY_DOWN, KEY_RIGHT, KEY_LEFT, KEY_A, KEY_B, KEY_X, KEY_Y}; const static char *keysLabels[] = {"\x18", "\x19", "\x1A", "\x1B", "", "", "", ""}; -bool randomConfirmBox(char* message) +bool randomConfirmBox(const char* message) { const int choiceRow = 10; int sequencePosition = 0; @@ -137,13 +137,13 @@ bool randomConfirmBox(char* message) return sequencePosition == sizeof(sequence); } -void messageBox(char* message) +void messageBox(const char* message) { clearScreen(&bottomScreen); messagePrint(message); } -void messagePrint(char* message) +void messagePrint(const char* message) { iprintf("%s\n", message); iprintf("\nOkay - [A]\n"); diff --git a/arm9/src/message.h b/arm9/src/message.h index 56d47af..a873a28 100644 --- a/arm9/src/message.h +++ b/arm9/src/message.h @@ -3,16 +3,24 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + enum { YES = true, NO = false }; void keyWait(u32 key); -bool choiceBox(char* message); -bool choicePrint(char* message); -bool randomConfirmBox(char* message); -void messageBox(char* message); -void messagePrint(char* message); +bool choiceBox(const char* message); +bool choicePrint(const char* message); +bool randomConfirmBox(const char* message); +void messageBox(const char* message); +void messagePrint(const char* message); + +#ifdef __cplusplus +} +#endif #endif \ No newline at end of file diff --git a/arm9/src/sha1digest.h b/arm9/src/sha1digest.h new file mode 100644 index 0000000..791230e --- /dev/null +++ b/arm9/src/sha1digest.h @@ -0,0 +1,48 @@ +#ifndef SHA1DIGEST_H +#define SHA1DIGEST_H + +#include +#include + +static constexpr int SHA1_LEN = 20; + +class Sha1Digest { +public: + friend constexpr inline bool operator==(const Sha1Digest& lhs, const Sha1Digest& rhs); + Sha1Digest() {} + constexpr Sha1Digest(std::string_view digestString) + { + auto hex2int = [](char ch) { + if (ch >= '0' && ch <= '9') return ch - '0'; + if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; + if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; + return -1; + }; + for(size_t i = 0; i < SHA1_LEN; ++i) { + auto c1 = digestString[i * 2]; + auto c2 = digestString[(i * 2) + 1]; + digest[i] = hex2int(c1) << 4 | hex2int(c2); + } + } + + auto data() + { + return digest.data(); + } + +private: + std::array digest{}; + +}; + +constexpr inline bool operator==(const Sha1Digest& lhs, const Sha1Digest& rhs) +{ + return lhs.digest == rhs.digest; +} + +consteval Sha1Digest operator ""_sha1(const char* args, size_t len) { + return Sha1Digest{std::string_view{args, len}}; +} + + +#endif \ No newline at end of file diff --git a/arm9/src/storage.c b/arm9/src/storage.c index 334127b..f7634c0 100644 --- a/arm9/src/storage.c +++ b/arm9/src/storage.c @@ -2,6 +2,7 @@ #include "main.h" #include "message.h" #include +#include #include #define TITLE_LIMIT 39 @@ -175,6 +176,40 @@ bool writeToFile(FILE* fd, const char* buffer, size_t size) return toWrite == 0; } +bool calculateFileSha1(FILE* f, void* digest) +{ + fseek(f, 0, SEEK_SET); + + swiSHA1context_t ctx; + ctx.sha_block = 0; //this is weird but it has to be done + swiSHA1Init(&ctx); + + char buffer[512]; + size_t n = 0; + while ((n = fread(buffer, sizeof(char), sizeof(buffer), f)) > 0) + { + swiSHA1Update(&ctx, buffer, n); + } + if (ferror(f) || !feof(f)) + { + return false; + } + swiSHA1Final(digest, &ctx); + return true; +} + +bool calculateFileSha1Path(const char* path, void* digest) +{ + FILE* targetFile = fopen(path, "rb"); + if (!targetFile) + { + return false; + } + bool res = calculateFileSha1(targetFile, digest); + fclose(targetFile); + return res; +} + bool safeCreateDir(const char* path) { if (((mkdir(path, 0777) == 0) || errno == EEXIST)) diff --git a/arm9/src/storage.h b/arm9/src/storage.h index e258749..7f1f8ab 100644 --- a/arm9/src/storage.h +++ b/arm9/src/storage.h @@ -4,6 +4,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + //Files bool fileExists(char const* path); int copyFile(char const* src, char const* dst); @@ -12,8 +16,14 @@ unsigned long long getFileSize(FILE* f); unsigned long long getFileSizePath(char const* path); bool toggleFileReadOnly(const char* path, bool readOnly); bool writeToFile(FILE* fd, const char* buffer, size_t size); +bool calculateFileSha1(FILE* f, void* digest); +bool calculateFileSha1Path(const char* path, void* digest); //Directories bool safeCreateDir(const char* path); +#ifdef __cplusplus +} +#endif + #endif \ No newline at end of file diff --git a/arm9/src/unlaunch.c b/arm9/src/unlaunch.cpp similarity index 75% rename from arm9/src/unlaunch.c rename to arm9/src/unlaunch.cpp index c99fc64..e1db1f4 100644 --- a/arm9/src/unlaunch.c +++ b/arm9/src/unlaunch.cpp @@ -1,6 +1,10 @@ #include "message.h" +#include "sha1digest.h" #include "storage.h" #include "unlaunch.h" +#include +#include +#include #include #include #include @@ -9,6 +13,37 @@ static char unlaunchInstallerBuffer[0x30000]; static const char* hnaaTmdPath = "nand:/title/00030017/484e4141/content/title.tmd"; static const char* hnaaBackupTmdPath = "nand:/title/00030017/484e4141/content/title.tmd.bak"; +enum UNLAUNCH_VERSION { + v1_8, + v1_9, + v2_0, + INVALID, +}; + +UNLAUNCH_VERSION installerVersion{INVALID}; +size_t unlaunchInstallerSize{}; + +constexpr std::array knownUnlaunchHashes{ + /*"9e6a8d95062533dfc422362f99ff3e24e7de9920"_sha1, // v0.8: blacklisted, doesn't like this install method*/ + /*"fb0d0ffebda67b786f608bf5cbcb2efee6ab42bb"_sha1, // v0.9: blacklisted, doesn't like this install method*/ + /*"d710ff585e321082b33456dd4e0568200c9adcc7"_sha1, // v1.0: blacklisted, doesn't like this install method*/ + /*"4f3e455e0a752d35a219a3ff10ba14a6c98bff13"_sha1, // v1.1: blacklisted, doesn't like this install method*/ + /*"25db1a47ba84748f911d9f4357bfc417533121c7"_sha1, // v1.2: blacklisted, doesn't like this install method*/ + /*"068f1d56da02bb4f93fb76d7874e14010c7e7a3d"_sha1, // v1.3: blacklisted, doesn't like this install method*/ + /*"43197370de74d302ef7c4420059c3ca7d50c4f3d"_sha1, // v1.4: blacklisted, doesn't like this install method*/ + /*"0525b28cc59b6f7fc00ad592aebadd7257bf7efb"_sha1, // v1.5: blacklisted, doesn't like this install method*/ + /*"9470a51fde188235052b119f6bfabf6689cb2343"_sha1, // v1.6: blacklisted, doesn't like this install method*/ + /*"672c11eb535b97b0d32ff580d314a2ad6411d5fe"_sha1, // v1.7: blacklisted, doesn't like this install method*/ + "b76c2b1722e769c6c0b4b3d4bc73250e41993229"_sha1, // v1.8 + "f3eb41cba136a3477523155f8b05df14917c55f4"_sha1, // v1.9 + "15f4a36251d1408d71114019b2825fe8f5b4c8cc"_sha1, // v2.0 +}; + +bool isValidUnlaunchInstallerSize(size_t size) +{ + return size == 163320 /*1.8*/ || size == 196088 /*1.9*/; +} + bool isLauncherTmdPatched(const char* path) { FILE* launcherTmd = fopen(path, "rb"); @@ -148,7 +183,10 @@ static bool writeUnlaunchTmd(const char* path) return false; } - if(!writeToFile(targetTmd, unlaunchInstallerBuffer, sizeof(unlaunchInstallerBuffer))) + Sha1Digest expectedDigest, actualDigest; + swiSHA1Calc(expectedDigest.data(), unlaunchInstallerBuffer, unlaunchInstallerSize + 520); + + if(!writeToFile(targetTmd, unlaunchInstallerBuffer, unlaunchInstallerSize + 520)) { fclose(targetTmd); remove(path); @@ -156,6 +194,14 @@ static bool writeUnlaunchTmd(const char* path) return false; } fclose(targetTmd); + calculateFileSha1Path(path, actualDigest.data()); + + if(expectedDigest != actualDigest) + { + remove(path); + messageBox("\x1B[31mError:\x1B[33m Unlaunch tmd was not properly written\n"); + return false; + } return true; } @@ -267,23 +313,23 @@ static bool installUnlaunchProtoConsole(void) return true; } -bool readUnlaunchInstaller(void) +bool readUnlaunchInstaller(const char* path) { - FILE* unlaunchInstaller = fopen("sd:/unlaunch.dsi", "rb"); + FILE* unlaunchInstaller = fopen(path, "rb"); if (!unlaunchInstaller) { messageBox("\x1B[31mError:\x1B[33m Failed to open unlaunch installer\n(sd:/unlaunch.dsi)\n"); return false; } - int toRead = sizeof(unlaunchInstallerBuffer) - 520; - size_t installerFilesize = getFileSize(unlaunchInstaller); - if(installerFilesize != toRead) + unlaunchInstallerSize = getFileSize(unlaunchInstaller); + if(isValidUnlaunchInstallerSize(unlaunchInstallerSize)) { messageBox("\x1B[31mError:\x1B[33m Unlaunch installer wrong file size\n"); return false; } + int toRead = unlaunchInstallerSize; char* buff = unlaunchInstallerBuffer; // Pad the installer with 520 bytes, those being the size of a valid tmd buff += 520; @@ -301,7 +347,22 @@ bool readUnlaunchInstaller(void) return false; } - fclose(unlaunchInstaller); + fclose(unlaunchInstaller); + return true; +} + +bool verifyUnlaunchInstaller(void) +{ + Sha1Digest digest; + swiSHA1Calc(digest.data(), unlaunchInstallerBuffer + 520, unlaunchInstallerSize); + auto it = std::ranges::find(knownUnlaunchHashes, digest); + if(it == knownUnlaunchHashes.end()) + { + messageBox("\x1B[31mError:\x1B[33m Provided unlaunch installer has an unknown hash\n"); + return false; + } + auto idx = std::distance(knownUnlaunchHashes.begin(), it); + installerVersion = static_cast(idx); return true; } @@ -311,9 +372,14 @@ bool patchUnlaunchInstaller(void) return true; } +bool loadUnlaunchInstaller(const char* path) +{ + return readUnlaunchInstaller(path) && verifyUnlaunchInstaller(); +} + bool installUnlaunch(bool retailConsole, const char* retailLauncherTmdPath) { - if (!readUnlaunchInstaller() || !patchUnlaunchInstaller()) + if (installerVersion == INVALID || !patchUnlaunchInstaller()) return false; // Treat protos differently diff --git a/arm9/src/unlaunch.h b/arm9/src/unlaunch.h index bbc5cee..9d9e76f 100644 --- a/arm9/src/unlaunch.h +++ b/arm9/src/unlaunch.h @@ -2,9 +2,19 @@ #define UNLAUNCH_H #include +#ifdef __cplusplus +extern "C" { +#endif + bool uninstallUnlaunch(bool notProto, const char* retailLauncherTmdPath); bool installUnlaunch(bool retailConsole, const char* retailLauncherTmdPath); bool isLauncherTmdPatched(const char* path); +bool loadUnlaunchInstaller(const char* path); + +#ifdef __cplusplus +} +#endif + #endif \ No newline at end of file