forked from Mirror/GodMode9
Merge remote-tracking branch 'origin/master' into mcuev
This commit is contained in:
commit
7d2e729ac6
43
.github/workflows/ci.yml
vendored
Normal file
43
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-18.04
|
||||
container: devkitpro/devkitarm:latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Fix apt sources
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get -y install dirmngr
|
||||
echo 'deb http://us.archive.ubuntu.com/ubuntu/ bionic main' >> /etc/apt/sources.list
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
|
||||
- name: Install and update packages
|
||||
run: |
|
||||
apt-get -y install python3 python3-pip p7zip-full libarchive13
|
||||
python3 --version
|
||||
python3 -m pip install --upgrade pip setuptools
|
||||
python3 -m pip install cryptography
|
||||
python3 -m pip install git+https://github.com/TuxSH/firmtool.git
|
||||
|
||||
- name: Build Project
|
||||
run: make release
|
||||
|
||||
- name: Prepare build artifact
|
||||
run: |
|
||||
cd release
|
||||
ZIPNAME=$(ls GodMode9-*.zip)
|
||||
rm $ZIPNAME
|
||||
echo "OUTNAME=${ZIPNAME%.zip}" >> $GITHUB_ENV
|
||||
cd ..
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ env.OUTNAME }}
|
||||
path: release/*
|
19
.travis.yml
19
.travis.yml
@ -1,19 +0,0 @@
|
||||
language: c
|
||||
sudo: true
|
||||
dist: bionic
|
||||
|
||||
before_install:
|
||||
- wget "https://github.com/devkitPro/pacman/releases/latest/download/devkitpro-pacman.amd64.deb" -O dkp-pacman.deb
|
||||
- export DEVKITPRO=/opt/devkitpro
|
||||
- export DEVKITARM=${DEVKITPRO}/devkitARM
|
||||
|
||||
install:
|
||||
- sudo apt-get -y install python3 python3-pip p7zip-full libarchive
|
||||
- sudo dpkg -i dkp-pacman.deb
|
||||
- sudo dkp-pacman -S --noconfirm devkitARM
|
||||
- sudo pip3 install --upgrade pip setuptools
|
||||
- sudo pip3 install cryptography
|
||||
- sudo pip3 install git+https://github.com/TuxSH/firmtool.git
|
||||
|
||||
script:
|
||||
- make release
|
@ -33,15 +33,16 @@
|
||||
#include "system/sys.h"
|
||||
#include "system/event.h"
|
||||
|
||||
#ifndef FIXED_BRIGHTNESS
|
||||
static const u8 brightness_lvls[] = {
|
||||
static const u8 brLvlTbl[] = {
|
||||
0x10, 0x17, 0x1E, 0x25,
|
||||
0x2C, 0x34, 0x3C, 0x44,
|
||||
0x4D, 0x56, 0x60, 0x6B,
|
||||
0x79, 0x8C, 0xA7, 0xD2
|
||||
};
|
||||
static int prev_bright_lvl;
|
||||
static bool auto_brightness;
|
||||
|
||||
#ifndef FIXED_BRIGHTNESS
|
||||
static int oldBrLvl;
|
||||
static bool autoBr;
|
||||
#endif
|
||||
|
||||
static SystemSHMEM __attribute__((section(".shared"))) sharedMem;
|
||||
@ -52,10 +53,10 @@ static void vblankUpdate(void)
|
||||
return;
|
||||
|
||||
#ifndef FIXED_BRIGHTNESS
|
||||
int cur_bright_lvl = (mcuGetVolumeSlider() >> 2) % countof(brightness_lvls);
|
||||
if ((cur_bright_lvl != prev_bright_lvl) && auto_brightness) {
|
||||
prev_bright_lvl = cur_bright_lvl;
|
||||
u8 br = brightness_lvls[cur_bright_lvl];
|
||||
int newBrLvl = (mcuGetVolumeSlider() >> 2) % countof(brLvlTbl);
|
||||
if ((newBrLvl != oldBrLvl) && autoBr) {
|
||||
oldBrLvl = newBrLvl;
|
||||
u8 br = brLvlTbl[newBrLvl];
|
||||
GFX_setBrightness(br, br);
|
||||
}
|
||||
#endif
|
||||
@ -92,10 +93,11 @@ void __attribute__((noreturn)) MainLoop(void)
|
||||
bool runPxiCmdProcessor = true;
|
||||
|
||||
#ifdef FIXED_BRIGHTNESS
|
||||
LCD_SetBrightness(FIXED_BRIGHTNESS);
|
||||
u8 fixBrLvl = brLvlTbl[clamp(FIXED_BRIGHTNESS, 0, countof(brLvlTbl)-1)];
|
||||
GFX_setBrightness(fixBrLvl, fixBrLvl);
|
||||
#else
|
||||
prev_bright_lvl = -1;
|
||||
auto_brightness = true;
|
||||
oldBrLvl = -1;
|
||||
autoBr = true;
|
||||
#endif
|
||||
|
||||
// initialize state stuff
|
||||
@ -119,7 +121,7 @@ void __attribute__((noreturn)) MainLoop(void)
|
||||
gicEnableInterrupt(VBLANK_INTERRUPT);
|
||||
|
||||
// ARM9 won't try anything funny until this point
|
||||
PXI_Barrier(ARM11_READY_BARRIER);
|
||||
PXI_Barrier(PXI_BOOT_BARRIER);
|
||||
|
||||
// Process commands until the ARM9 tells
|
||||
// us it's time to boot something else
|
||||
@ -190,10 +192,10 @@ void __attribute__((noreturn)) MainLoop(void)
|
||||
#ifndef FIXED_BRIGHTNESS
|
||||
if ((newbrightness > 0) && (newbrightness < 0x100)) {
|
||||
GFX_setBrightness(newbrightness, newbrightness);
|
||||
auto_brightness = false;
|
||||
autoBr = false;
|
||||
} else {
|
||||
prev_bright_lvl = -1;
|
||||
auto_brightness = true;
|
||||
oldBrLvl = -1;
|
||||
autoBr = true;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
@ -219,6 +221,9 @@ void __attribute__((noreturn)) MainLoop(void)
|
||||
|
||||
gicDisableInterrupt(MCU_INTERRUPT);
|
||||
|
||||
// Wait for the ARM9 to do its firmlaunch setup
|
||||
PXI_Barrier(PXI_FIRMLAUNCH_BARRIER);
|
||||
|
||||
SYS_CoreZeroShutdown();
|
||||
SYS_CoreShutdown();
|
||||
}
|
||||
|
@ -58,10 +58,7 @@ static bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout) {
|
||||
}
|
||||
|
||||
// next row
|
||||
if (layout[l++] != 0) {
|
||||
ShowPrompt(false, "Oh shit %lu %lu", k, l);
|
||||
return false; // error!!!! THIS HAS TO GO!
|
||||
}
|
||||
if (layout[l++] != 0) return false;
|
||||
p_y += SWKBD_STDKEY_HEIGHT + SWKDB_KEY_SPACING;
|
||||
}
|
||||
|
||||
@ -136,7 +133,7 @@ static void DrawTextBox(const TouchBox* txtbox, const char* inputstr, const u32
|
||||
(*scroll) ? '<' : '|',
|
||||
(inputstr_size > input_shown) ? input_shown : inputstr_size,
|
||||
(inputstr_size > input_shown) ? input_shown : inputstr_size,
|
||||
inputstr + *scroll,
|
||||
(*scroll > inputstr_size) ? "" : inputstr + *scroll,
|
||||
(inputstr_size > input_shown) ? 0 : input_shown - inputstr_size,
|
||||
(inputstr_size > input_shown) ? 0 : input_shown - inputstr_size,
|
||||
"",
|
||||
@ -199,6 +196,7 @@ static char KeyboardWait(TouchBox* swkbd, bool uppercase) {
|
||||
else if (pressed & BUTTON_R1) return KEY_CAPS;
|
||||
else if (pressed & BUTTON_RIGHT) return KEY_RIGHT;
|
||||
else if (pressed & BUTTON_LEFT) return KEY_LEFT;
|
||||
else if (pressed & BUTTON_SELECT) return KEY_SWITCH;
|
||||
else if (pressed & BUTTON_TOUCH) break;
|
||||
}
|
||||
|
||||
@ -297,6 +295,9 @@ bool ShowKeyboard(char* inputstr, const u32 max_size, const char *format, ...) {
|
||||
swkbd = swkbd_special;
|
||||
} else if (key == KEY_NUMPAD) {
|
||||
swkbd = swkbd_numpad;
|
||||
} else if (key == KEY_SWITCH) {
|
||||
ClearScreen(BOT_SCREEN, COLOR_STD_BG);
|
||||
return ShowStringPrompt(inputstr, max_size, str);
|
||||
} else if (key && (key < 0x80)) {
|
||||
if ((cursor < (max_size-1)) && (inputstr_size < max_size)) {
|
||||
// pad string (if cursor beyound string size)
|
||||
|
@ -18,16 +18,16 @@ enum {
|
||||
KEY_LEFT = 0x88,
|
||||
KEY_RIGHT = 0x89,
|
||||
KEY_ESCAPE = 0x8A,
|
||||
KEY_SWITCH = 0x8B,
|
||||
KEY_TXTBOX = 0xFF
|
||||
};
|
||||
|
||||
// special key strings
|
||||
#define SWKBD_KEYSTR "", "DEL", "INS", "SUBMIT", "CAPS", "#$@", "123", "ABC", "\x1b", "\x1a", "ESC"
|
||||
#define SWKBD_KEYSTR "", "DEL", "INS", "SUBMIT", "CAPS", "#$@", "123", "ABC", "\x1b", "\x1a", "ESC", "SWITCH"
|
||||
|
||||
#define COLOR_SWKBD_NORMAL COLOR_GREY
|
||||
#define COLOR_SWKBD_PRESSED COLOR_LIGHTGREY
|
||||
#define COLOR_SWKBD_BOX COLOR_DARKGREY
|
||||
#define COLOR_SWKBD_TEXTBOX COLOR_DARKGREY
|
||||
#define COLOR_SWKBD_CHARS COLOR_BLACK
|
||||
#define COLOR_SWKBD_ENTER COLOR_TINTEDBLUE
|
||||
#define COLOR_SWKBD_CAPS COLOR_TINTEDYELLOW
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "filetype.h"
|
||||
#include "fsutil.h"
|
||||
#include "image.h"
|
||||
#include "fatmbr.h"
|
||||
#include "nand.h"
|
||||
#include "game.h"
|
||||
@ -112,7 +113,16 @@ u64 IdentifyFileType(const char* path) {
|
||||
}
|
||||
}
|
||||
|
||||
if (GetFontFromPbm(data, fsize, NULL, NULL)) {
|
||||
if (fsize == sizeof(TitleInfoEntry) && (strncasecmp(path, "T:/", 3) == 0)) {
|
||||
const char* mntpath = GetMountPath();
|
||||
if (mntpath && *mntpath) {
|
||||
if ((strncasecmp(mntpath, "1:/dbs/title.db", 16) == 0) ||
|
||||
(strncasecmp(mntpath, "4:/dbs/title.db", 16) == 0) ||
|
||||
(strncasecmp(mntpath, "A:/dbs/title.db", 16) == 0) ||
|
||||
(strncasecmp(mntpath, "B:/dbs/title.db", 16) == 0))
|
||||
return GAME_TIE;
|
||||
}
|
||||
} else if (GetFontFromPbm(data, fsize, NULL, NULL)) {
|
||||
return FONT_PBM;
|
||||
} else if ((fsize > sizeof(AgbHeader)) &&
|
||||
(ValidateAgbHeader((AgbHeader*) data) == 0)) {
|
||||
|
@ -14,27 +14,28 @@
|
||||
#define GAME_BOSS (1ULL<<9)
|
||||
#define GAME_NUSCDN (1ULL<<10)
|
||||
#define GAME_TICKET (1ULL<<11)
|
||||
#define GAME_SMDH (1ULL<<12)
|
||||
#define GAME_3DSX (1ULL<<13)
|
||||
#define GAME_NDS (1ULL<<14)
|
||||
#define GAME_GBA (1ULL<<15)
|
||||
#define GAME_TAD (1ULL<<16)
|
||||
#define SYS_FIRM (1ULL<<17)
|
||||
#define SYS_DIFF (1ULL<<18)
|
||||
#define SYS_DISA (1ULL<<19)
|
||||
#define SYS_AGBSAVE (1ULL<<20)
|
||||
#define SYS_TICKDB (1ULL<<21)
|
||||
#define BIN_NCCHNFO (1ULL<<22)
|
||||
#define BIN_TIKDB (1ULL<<23)
|
||||
#define BIN_KEYDB (1ULL<<24)
|
||||
#define BIN_LEGKEY (1ULL<<25)
|
||||
#define TXT_SCRIPT (1ULL<<26)
|
||||
#define TXT_GENERIC (1ULL<<27)
|
||||
#define GFX_PNG (1ULL<<28)
|
||||
#define FONT_PBM (1ULL<<29)
|
||||
#define NOIMG_NAND (1ULL<<30)
|
||||
#define HDR_NAND (1ULL<<31)
|
||||
#define TYPE_BASE 0xFFFFFFFFULL // 32 bit reserved for base types
|
||||
#define GAME_TIE (1ULL<<12)
|
||||
#define GAME_SMDH (1ULL<<13)
|
||||
#define GAME_3DSX (1ULL<<14)
|
||||
#define GAME_NDS (1ULL<<15)
|
||||
#define GAME_GBA (1ULL<<16)
|
||||
#define GAME_TAD (1ULL<<17)
|
||||
#define SYS_FIRM (1ULL<<18)
|
||||
#define SYS_DIFF (1ULL<<19)
|
||||
#define SYS_DISA (1ULL<<20)
|
||||
#define SYS_AGBSAVE (1ULL<<21)
|
||||
#define SYS_TICKDB (1ULL<<22)
|
||||
#define BIN_NCCHNFO (1ULL<<23)
|
||||
#define BIN_TIKDB (1ULL<<24)
|
||||
#define BIN_KEYDB (1ULL<<25)
|
||||
#define BIN_LEGKEY (1ULL<<26)
|
||||
#define TXT_SCRIPT (1ULL<<27)
|
||||
#define TXT_GENERIC (1ULL<<28)
|
||||
#define GFX_PNG (1ULL<<29)
|
||||
#define FONT_PBM (1ULL<<30)
|
||||
#define NOIMG_NAND (1ULL<<31)
|
||||
#define HDR_NAND (1ULL<<32)
|
||||
#define TYPE_BASE 0xFFFFFFFFFFULL // 40 bit reserved for base types
|
||||
|
||||
// #define FLAG_FIRM (1ULL<<57) // <--- for CXIs containing FIRMs
|
||||
// #define FLAG_GBAVC (1ULL<<58) // <--- for GBAVC CXIs
|
||||
@ -45,16 +46,17 @@
|
||||
#define FLAG_CXI (1ULL<<63)
|
||||
|
||||
#define FTYPE_MOUNTABLE(tp) (tp&(IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|GAME_NDS|GAME_TAD|SYS_FIRM|SYS_DIFF|SYS_DISA|SYS_TICKDB|BIN_KEYDB))
|
||||
#define FTYPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_BOSS|SYS_FIRM))
|
||||
#define FTYPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_TIE|GAME_BOSS|SYS_FIRM))
|
||||
#define FTYPE_DECRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|GAME_NUSCDN|SYS_FIRM|BIN_KEYDB))
|
||||
#define FTYPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|BIN_KEYDB))
|
||||
#define FTYPE_CIABUILD(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_TMD)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW))))
|
||||
#define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD)))
|
||||
#define FTYPE_CIABUILD(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_TIE)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW))))
|
||||
#define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD|GAME_TIE)))
|
||||
#define FTYPE_CIAINSTALL(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_CIA)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW))) || (tp&(GAME_TMD)&&(tp&(FLAG_NUSCDN))))
|
||||
#define FTYPE_CXIDUMP(tp) (tp&(GAME_TMD))
|
||||
#define FTYPE_UNINSTALL(tp) (tp&(GAME_TIE))
|
||||
#define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB))
|
||||
#define FTYPE_KEYBUILD(tp) (tp&(BIN_KEYDB|BIN_LEGKEY))
|
||||
#define FTYPE_TITLEINFO(tp) (tp&(GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS|GAME_GBA|GAME_TAD|GAME_3DSX))
|
||||
#define FTYPE_TITLEINFO(tp) (tp&(GAME_TIE|GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS|GAME_GBA|GAME_TAD|GAME_3DSX))
|
||||
#define FTYPE_CIACHECK(tp) (tp&GAME_CIA)
|
||||
#define FTYPE_RENAMABLE(tp) (tp&(GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_NDS|GAME_GBA))
|
||||
#define FTYPE_TRIMABLE(tp) (tp&(IMG_NAND|GAME_NCCH|GAME_NCSD|GAME_NDS|SYS_FIRM))
|
||||
|
@ -60,7 +60,6 @@ const char* GetMountPath(void) {
|
||||
}
|
||||
|
||||
u64 MountImage(const char* path) {
|
||||
u64 type = (path) ? IdentifyFileType(path) : 0;
|
||||
if (mount_state) {
|
||||
fvx_close(&mount_file);
|
||||
if (fix_cmac) FixFileCmac(mount_path, false);
|
||||
@ -68,6 +67,7 @@ u64 MountImage(const char* path) {
|
||||
mount_state = 0;
|
||||
*mount_path = 0;
|
||||
}
|
||||
u64 type = (path) ? IdentifyFileType(path) : 0;
|
||||
if (!type) return 0;
|
||||
if ((fvx_open(&mount_file, path, FA_READ | FA_WRITE | FA_OPEN_EXISTING) != FR_OK) &&
|
||||
(fvx_open(&mount_file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK))
|
||||
|
@ -193,6 +193,44 @@ FRESULT fvx_qwrite (const TCHAR* path, const void* buff, FSIZE_t ofs, UINT btw,
|
||||
return res;
|
||||
}
|
||||
|
||||
FRESULT fvx_qcreate (const TCHAR* path, UINT btc) {
|
||||
FIL fp;
|
||||
FRESULT res;
|
||||
|
||||
res = fvx_open(&fp, path, FA_WRITE | FA_CREATE_ALWAYS);
|
||||
if (res != FR_OK) return res;
|
||||
|
||||
res = fvx_lseek(&fp, btc);
|
||||
fvx_close(&fp);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* // untested / unused, might come in handy at a later point
|
||||
FRESULT fvx_qfill (const TCHAR* path, const void* buff, UINT btb) {
|
||||
FIL fp;
|
||||
FRESULT res;
|
||||
UINT bwtt = 0;
|
||||
UINT fsiz = 0;
|
||||
|
||||
res = fvx_open(&fp, path, FA_WRITE | FA_OPEN_EXISTING);
|
||||
if (res != FR_OK) return res;
|
||||
|
||||
fsiz = fvx_size(&fp);
|
||||
while (bwtt < fsiz) {
|
||||
UINT btw = ((fsiz - bwtt) >= btb) ? btb : (fsiz - bwtt);
|
||||
UINT bwt;
|
||||
|
||||
res = fvx_write(&fp, buff, btw, &bwt);
|
||||
if ((res == FR_OK) && (bwt != btw)) res = FR_DENIED;
|
||||
if (res != FR_OK) break;
|
||||
bwtt += bwt;
|
||||
}
|
||||
fvx_close(&fp);
|
||||
|
||||
return res;
|
||||
}*/
|
||||
|
||||
FSIZE_t fvx_qsize (const TCHAR* path) {
|
||||
FILINFO fno;
|
||||
return (fvx_stat(path, &fno) == FR_OK) ? fno.fsize : 0;
|
||||
|
@ -29,9 +29,10 @@ FRESULT fvx_opendir (DIR* dp, const TCHAR* path);
|
||||
FRESULT fvx_closedir (DIR* dp);
|
||||
FRESULT fvx_readdir (DIR* dp, FILINFO* fno);
|
||||
|
||||
// additional quick read / write functions
|
||||
// additional quick read / write / create functions
|
||||
FRESULT fvx_qread (const TCHAR* path, void* buff, FSIZE_t ofs, UINT btr, UINT* br);
|
||||
FRESULT fvx_qwrite (const TCHAR* path, const void* buff, FSIZE_t ofs, UINT btw, UINT* bw);
|
||||
FRESULT fvx_qcreate (const TCHAR* path, UINT btc);
|
||||
|
||||
// additional quick file info functions
|
||||
FSIZE_t fvx_qsize (const TCHAR* path);
|
||||
|
@ -737,6 +737,7 @@ u32 ReadTicketFromDB(const char* path, const u8* title_id, Ticket** ticket) {
|
||||
TicketEntry* te = NULL;
|
||||
u32 entry_size;
|
||||
|
||||
*ticket = NULL;
|
||||
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
@ -1,10 +1,7 @@
|
||||
#include "ncch.h"
|
||||
#include "support.h"
|
||||
#include "disadiff.h"
|
||||
#include "keydb.h"
|
||||
#include "aes.h"
|
||||
#include "sha.h"
|
||||
#include "ff.h"
|
||||
|
||||
#define EXEFS_KEYID(name) (((strncmp(name, "banner", 8) == 0) || (strncmp(name, "icon", 8) == 0)) ? 0 : 1)
|
||||
|
||||
@ -51,100 +48,6 @@ u32 GetNcchCtr(u8* ctr, NcchHeader* ncch, u8 section) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 GetNcchSeed(u8* seed, NcchHeader* ncch) {
|
||||
static u8 lseed[16+8] __attribute__((aligned(4))) = { 0 }; // seed plus title ID for easy validation
|
||||
u64 titleId = ncch->programId;
|
||||
u32 hash_seed = ncch->hash_seed;
|
||||
u32 sha256sum[8];
|
||||
|
||||
memcpy(lseed+16, &(ncch->programId), 8);
|
||||
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
||||
if (hash_seed == sha256sum[0]) {
|
||||
memcpy(seed, lseed, 16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// setup a large enough buffer
|
||||
u8* buffer = (u8*) malloc(max(STD_BUFFER_SIZE, SEEDSAVE_AREA_SIZE));
|
||||
if (!buffer) return 1;
|
||||
|
||||
// try to grab the seed from NAND database
|
||||
const char* nand_drv[] = {"1:", "4:"}; // SysNAND and EmuNAND
|
||||
for (u32 i = 0; i < countof(nand_drv); i++) {
|
||||
UINT btr = 0;
|
||||
FIL file;
|
||||
char path[128];
|
||||
|
||||
// grab the key Y from movable.sed
|
||||
u8 movable_keyy[16];
|
||||
snprintf(path, 128, "%s/private/movable.sed", nand_drv[i]);
|
||||
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
continue;
|
||||
f_lseek(&file, 0x110);
|
||||
f_read(&file, movable_keyy, 0x10, &btr);
|
||||
f_close(&file);
|
||||
|
||||
// build the seed save path
|
||||
sha_quick(sha256sum, movable_keyy, 0x10, SHA256_MODE);
|
||||
snprintf(path, 128, "%s/data/%08lX%08lX%08lX%08lX/sysdata/0001000F/00000000",
|
||||
nand_drv[i], sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]);
|
||||
|
||||
// check seedsave for seed
|
||||
u8* seeddb = buffer;
|
||||
if (ReadDisaDiffIvfcLvl4(path, NULL, SEEDSAVE_AREA_OFFSET, SEEDSAVE_AREA_SIZE, seeddb) != SEEDSAVE_AREA_SIZE)
|
||||
continue;
|
||||
|
||||
// search for the seed
|
||||
for (u32 s = 0; s < SEEDSAVE_MAX_ENTRIES; s++) {
|
||||
if (titleId != getle64(seeddb + (s*8))) continue;
|
||||
memcpy(lseed, seeddb + (SEEDSAVE_MAX_ENTRIES*8) + (s*16), 16);
|
||||
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
||||
if (hash_seed == sha256sum[0]) {
|
||||
memcpy(seed, lseed, 16);
|
||||
free(buffer);
|
||||
return 0; // found!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// not found -> try seeddb.bin
|
||||
SeedInfo* seeddb = (SeedInfo*) (void*) buffer;
|
||||
size_t len = LoadSupportFile(SEEDDB_NAME, seeddb, STD_BUFFER_SIZE);
|
||||
if (len && (seeddb->n_entries <= (len - 16) / 32)) { // check filesize / seeddb size
|
||||
for (u32 s = 0; s < seeddb->n_entries; s++) {
|
||||
if (titleId != seeddb->entries[s].titleId)
|
||||
continue;
|
||||
memcpy(lseed, seeddb->entries[s].seed, 16);
|
||||
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
||||
if (hash_seed == sha256sum[0]) {
|
||||
memcpy(seed, lseed, 16);
|
||||
free(buffer);
|
||||
return 0; // found!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// out of options -> failed!
|
||||
free(buffer);
|
||||
return 1;
|
||||
}
|
||||
|
||||
u32 AddSeedToDb(SeedInfo* seed_info, SeedInfoEntry* seed_entry) {
|
||||
if (!seed_entry) { // no seed entry -> reset database
|
||||
memset(seed_info, 0, 16);
|
||||
return 0;
|
||||
}
|
||||
// check if entry already in DB
|
||||
u32 n_entries = seed_info->n_entries;
|
||||
SeedInfoEntry* seed = seed_info->entries;
|
||||
for (u32 i = 0; i < n_entries; i++, seed++)
|
||||
if (seed->titleId == seed_entry->titleId) return 0;
|
||||
// actually a new seed entry
|
||||
memcpy(seed, seed_entry, sizeof(SeedInfoEntry));
|
||||
seed_info->n_entries++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid) {
|
||||
u8 flags3 = (crypto >> 8) & 0xFF;
|
||||
u8 flags7 = crypto & 0xFF;
|
||||
@ -177,7 +80,7 @@ u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid) {
|
||||
if ((memcmp(lsignature, ncch->signature, 16) != 0) || (ltitleId != ncch->programId)) {
|
||||
u8 keydata[16+16] __attribute__((aligned(4)));
|
||||
memcpy(keydata, ncch->signature, 16);
|
||||
if (GetNcchSeed(keydata + 16, ncch) != 0)
|
||||
if (FindSeed(keydata + 16, ncch->programId, ncch->hash_seed) != 0)
|
||||
return 1;
|
||||
sha_quick(seedkeyY, keydata, 32, SHA256_MODE);
|
||||
memcpy(lsignature, ncch->signature, 16);
|
||||
@ -357,3 +260,12 @@ u32 SetNcchSdFlag(void* data) { // data must be at least 0x600 byte and start wi
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 SetupSystemForNcch(NcchHeader* ncch, bool to_emunand) {
|
||||
u16 crypto = NCCH_GET_CRYPTO(ncch);
|
||||
if ((crypto & 0x20) && // seed crypto
|
||||
(SetupSeedSystemCrypto(ncch->programId, ncch->hash_seed, to_emunand) != 0) &&
|
||||
(SetupSeedPrePurchase(ncch->programId, to_emunand) != 0))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "exefs.h"
|
||||
#include "seedsave.h"
|
||||
|
||||
#define NCCH_MEDIA_UNIT 0x200
|
||||
|
||||
@ -17,13 +18,6 @@
|
||||
#define NCCH_STDCRYPTO 0x0000
|
||||
#define NCCH_GET_CRYPTO(ncch) (!NCCH_ENCRYPTED(ncch) ? NCCH_NOCRYPTO : (((ncch)->flags[3] << 8) | ((ncch)->flags[7]&(0x01|0x20))))
|
||||
|
||||
#define SEEDDB_NAME "seeddb.bin"
|
||||
#define SEEDDB_SIZE(sdb) (16 + ((sdb)->n_entries * sizeof(SeedInfoEntry)))
|
||||
|
||||
#define SEEDSAVE_MAX_ENTRIES 2000
|
||||
#define SEEDSAVE_AREA_OFFSET 0x4000
|
||||
#define SEEDSAVE_AREA_SIZE (SEEDSAVE_MAX_ENTRIES * (8+16))
|
||||
|
||||
// wrapper defines
|
||||
#define DecryptNcch(data, offset, size, ncch, exefs) CryptNcch(data, offset, size, ncch, exefs, NCCH_NOCRYPTO)
|
||||
#define EncryptNcch(data, offset, size, ncch, exefs, crypto) CryptNcch(data, offset, size, ncch, exefs, crypto)
|
||||
@ -89,23 +83,10 @@ typedef struct {
|
||||
u8 hash_romfs[0x20];
|
||||
} __attribute__((packed, aligned(16))) NcchHeader;
|
||||
|
||||
typedef struct {
|
||||
u64 titleId;
|
||||
u8 seed[16];
|
||||
u8 reserved[8];
|
||||
} PACKED_STRUCT SeedInfoEntry;
|
||||
|
||||
typedef struct {
|
||||
u32 n_entries;
|
||||
u8 padding[12];
|
||||
SeedInfoEntry entries[256]; // this number is only a placeholder
|
||||
} PACKED_STRUCT SeedInfo;
|
||||
|
||||
u32 ValidateNcchHeader(NcchHeader* header);
|
||||
u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid);
|
||||
u32 SetupNcchCrypto(NcchHeader* ncch, u16 crypt_to);
|
||||
u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* exefs, u16 crypto);
|
||||
u32 CryptNcchSequential(void* data, u32 offset, u32 size, u16 crypto);
|
||||
u32 SetNcchSdFlag(void* data);
|
||||
|
||||
u32 AddSeedToDb(SeedInfo* seed_info, SeedInfoEntry* seed_entry);
|
||||
u32 SetupSystemForNcch(NcchHeader* ncch, bool to_emunand);
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "nds.h"
|
||||
#include "fatmbr.h"
|
||||
#include "vff.h"
|
||||
#include "crc16.h"
|
||||
#include "utf.h"
|
||||
@ -26,6 +27,66 @@ u32 ValidateTwlHeader(TwlHeader* twl) {
|
||||
return (crc16_quick(twl->logo, sizeof(twl->logo)) == NDS_LOGO_CRC16) ? 0 : 1;
|
||||
}
|
||||
|
||||
u32 BuildTwlSaveHeader(void* sav, u32 size) {
|
||||
const u16 sct_size = 0x200;
|
||||
if (size / (u32) sct_size > 0xFFFF)
|
||||
return 1;
|
||||
|
||||
// fit max number of sectors into size
|
||||
// that's how Nintendo does it ¯\_(ツ)_/¯
|
||||
const u16 n_sct_max = size / sct_size;
|
||||
u16 n_sct = 1;
|
||||
u16 sct_track = 1;
|
||||
u16 sct_heads = 1;
|
||||
while (true) {
|
||||
if (sct_heads < sct_track) {
|
||||
u16 n_sct_next = sct_track * (sct_heads+1) * (sct_heads+1);
|
||||
if (n_sct_next < n_sct_max) {
|
||||
sct_heads++;
|
||||
n_sct = n_sct_next;
|
||||
} else break;
|
||||
} else {
|
||||
u16 n_sct_next = (sct_track+1) * sct_heads * sct_heads;
|
||||
if (n_sct_next < n_sct_max) {
|
||||
sct_track++;
|
||||
n_sct = n_sct_next;
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
// sectors per cluster (should be identical to Nintendo)
|
||||
u8 clr_size = (n_sct > 8 * 1024) ? 8 : (n_sct > 1024) ? 4 : 1;
|
||||
|
||||
// how many FAT sectors do we need?
|
||||
u16 tot_clr = align(n_sct, clr_size) / clr_size;
|
||||
u32 fat_byte = (align(tot_clr, 2) / 2) * 3; // 2 sectors -> 3 byte
|
||||
u16 fat_size = align(fat_byte, sct_size) / sct_size;
|
||||
|
||||
// build the FAT header
|
||||
Fat16Header* fat = sav;
|
||||
memset(fat, 0x00, sizeof(Fat16Header));
|
||||
fat->jmp[0] = 0xE9; // E9 00 00
|
||||
memcpy(fat->oemname, "MSWIN4.1", 8);
|
||||
fat->sct_size = sct_size; // 512 byte / sector
|
||||
fat->clr_size = clr_size; // sectors per cluster
|
||||
fat->sct_reserved = 0x0001; // 1 reserved sector
|
||||
fat->fat_n = 0x02; // 2 FATs
|
||||
fat->root_n = 0x0020; // 32 root dir entries (2 sectors)
|
||||
fat->reserved0 = n_sct; // sectors in filesystem
|
||||
fat->mediatype = 0xF8; // "hard disk"
|
||||
fat->fat_size = fat_size; // sectors per fat (1 sector)
|
||||
fat->sct_track = sct_track; // sectors per track (legacy? see above)
|
||||
fat->sct_heads = sct_heads; // sectors per head (legacy? see above)
|
||||
fat->ndrive = 0x05; // for whatever reason
|
||||
fat->boot_sig = 0x29; // "boot signature"
|
||||
fat->vol_id = 0x12345678; // volume id
|
||||
memcpy(fat->vol_label, "VOLUMELABEL", 11); // standard volume label
|
||||
memcpy(fat->fs_type, "FAT12 ", 8); // filesystem type
|
||||
fat->magic = 0xAA55;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 LoadTwlMetaData(const char* path, TwlHeader* hdr, TwlIconData* icon) {
|
||||
u8 ALIGN(32) ntr_header[0x200]; // we only need the NTR header (ignore TWL stuff)
|
||||
TwlHeader* twl = hdr ? hdr : (void*) ntr_header;
|
||||
|
@ -102,28 +102,32 @@ typedef struct {
|
||||
// extended mode stuff (DSi only)
|
||||
u8 ignored0[0x30]; // ignored
|
||||
u32 region_flags;
|
||||
u8 ignored1[0xC]; // ignored
|
||||
u32 access_control;
|
||||
u32 arm7_scfg_ext7;
|
||||
u8 reserved2[3];
|
||||
u8 srl_flag;
|
||||
u32 arm9i_rom_offset;
|
||||
u32 reserved2;
|
||||
u32 reserved3;
|
||||
u32 arm9i_load_adress;
|
||||
u32 arm9i_size;
|
||||
u32 arm7i_rom_offset;
|
||||
u32 unknown1;
|
||||
u32 arm7i_load_adress;
|
||||
u32 arm7i_size;
|
||||
u8 ignored2[0x30]; // ignored
|
||||
u8 ignored1[0x30]; // ignored
|
||||
u32 ntr_twl_rom_size;
|
||||
u8 unknown2[12];
|
||||
u8 ignored3[0x10]; // ignored
|
||||
u8 ignored2[0x10]; // ignored
|
||||
u64 title_id;
|
||||
u32 pubsav_size;
|
||||
u32 prvsav_size;
|
||||
u8 reserved3[176];
|
||||
u8 reserved4[176];
|
||||
u8 unknown3[0x10];
|
||||
u8 ignored4[0xD00]; // ignored
|
||||
u8 ignored3[0xD00]; // ignored
|
||||
} PACKED_STRUCT TwlHeader;
|
||||
|
||||
u32 ValidateTwlHeader(TwlHeader* twl);
|
||||
u32 BuildTwlSaveHeader(void* sav, u32 size);
|
||||
u32 LoadTwlMetaData(const char* path, TwlHeader* hdr, TwlIconData* icon);
|
||||
u32 GetTwlTitle(char* desc, const TwlIconData* twl_icon);
|
||||
u32 GetTwlIcon(u16* icon, const TwlIconData* twl_icon);
|
||||
|
240
arm9/source/game/seedsave.c
Normal file
240
arm9/source/game/seedsave.c
Normal file
@ -0,0 +1,240 @@
|
||||
#include "seedsave.h"
|
||||
#include "support.h"
|
||||
#include "nandcmac.h"
|
||||
#include "sha.h"
|
||||
#include "ff.h"
|
||||
|
||||
#define TITLETAG_MAX_ENTRIES 2000 // same as SEEDSAVE_MAX_ENTRIES
|
||||
#define TITLETAG_AREA_OFFSET 0x10000 // thanks @luigoalma
|
||||
|
||||
// this structure is 0x80 bytes, thanks @luigoalma
|
||||
typedef struct {
|
||||
char magic[4]; // "PREP" for prepurchase install. NIM excepts "PREP" to do seed downloads on the background.
|
||||
// playable date parameters
|
||||
// 2000-01-01 is a safe bet for a stub entry
|
||||
s32 year;
|
||||
u8 month;
|
||||
u8 day;
|
||||
u16 country_code; // enum list of values, this will affect seed downloading, just requires at least one valid enum value. 1 == Japan, it's enough.
|
||||
// everything after this point can be 0 padded
|
||||
u32 seed_status; // 0 == not tried, 1 == last attempt failed, 2 == seed downloaded successfully
|
||||
s32 seed_result; // result related to last download attempt
|
||||
s32 seed_support_error_code; // support code derived from the result code
|
||||
// after this point, all is unused or padding. NIM wont use or access this at all.
|
||||
// It's memset to 0 by NIM
|
||||
u8 unknown[0x68];
|
||||
} PACKED_STRUCT TitleTagEntry;
|
||||
|
||||
typedef struct {
|
||||
u32 unknown0;
|
||||
u32 n_entries;
|
||||
u8 unknown1[0x1000 - 0x8];
|
||||
u64 titleId[TITLETAG_MAX_ENTRIES];
|
||||
TitleTagEntry tag[TITLETAG_MAX_ENTRIES];
|
||||
} PACKED_STRUCT TitleTag;
|
||||
|
||||
u32 GetSeedPath(char* path, const char* drv) {
|
||||
u8 movable_keyy[16] = { 0 };
|
||||
u32 sha256sum[8];
|
||||
UINT btr = 0;
|
||||
FIL file;
|
||||
|
||||
// grab the key Y from movable.sed
|
||||
// wrong result if movable.sed does not have it
|
||||
snprintf(path, 128, "%2.2s/private/movable.sed", drv);
|
||||
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
f_lseek(&file, 0x110);
|
||||
f_read(&file, movable_keyy, 0x10, &btr);
|
||||
f_close(&file);
|
||||
if (btr != 0x10)
|
||||
return 1;
|
||||
|
||||
// build the seed save path
|
||||
sha_quick(sha256sum, movable_keyy, 0x10, SHA256_MODE);
|
||||
snprintf(path, 128, "%2.2s/data/%08lX%08lX%08lX%08lX/sysdata/0001000F/00000000",
|
||||
drv, sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 FindSeed(u8* seed, u64 titleId, u32 hash_seed) {
|
||||
static u8 lseed[16+8] __attribute__((aligned(4))) = { 0 }; // seed plus title ID for easy validation
|
||||
u32 sha256sum[8];
|
||||
|
||||
memcpy(lseed+16, &titleId, 8);
|
||||
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
||||
if (hash_seed == sha256sum[0]) {
|
||||
memcpy(seed, lseed, 16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// setup a large enough buffer
|
||||
u8* buffer = (u8*) malloc(max(STD_BUFFER_SIZE, sizeof(SeedDb)));
|
||||
if (!buffer) return 1;
|
||||
|
||||
// try to grab the seed from NAND database
|
||||
const char* nand_drv[] = {"1:", "4:"}; // SysNAND and EmuNAND
|
||||
for (u32 i = 0; i < countof(nand_drv); i++) {
|
||||
char path[128];
|
||||
SeedDb* seeddb = (SeedDb*) (void*) buffer;
|
||||
|
||||
// read SEEDDB from file
|
||||
if (GetSeedPath(path, nand_drv[i]) != 0) continue;
|
||||
if ((ReadDisaDiffIvfcLvl4(path, NULL, SEEDSAVE_AREA_OFFSET, sizeof(SeedDb), seeddb) != sizeof(SeedDb)) ||
|
||||
(seeddb->n_entries > SEEDSAVE_MAX_ENTRIES))
|
||||
continue;
|
||||
|
||||
// search for the seed
|
||||
for (u32 s = 0; s < seeddb->n_entries; s++) {
|
||||
if (titleId != seeddb->titleId[s]) continue;
|
||||
memcpy(lseed, &(seeddb->seed[s]), sizeof(Seed));
|
||||
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
||||
if (hash_seed == sha256sum[0]) {
|
||||
memcpy(seed, lseed, 16);
|
||||
free(buffer);
|
||||
return 0; // found!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// not found -> try seeddb.bin
|
||||
SeedInfo* seeddb = (SeedInfo*) (void*) buffer;
|
||||
size_t len = LoadSupportFile(SEEDINFO_NAME, seeddb, STD_BUFFER_SIZE);
|
||||
if (len && (seeddb->n_entries <= (len - 16) / 32)) { // check filesize / seeddb size
|
||||
for (u32 s = 0; s < seeddb->n_entries; s++) {
|
||||
if (titleId != seeddb->entries[s].titleId)
|
||||
continue;
|
||||
memcpy(lseed, &(seeddb->entries[s].seed), sizeof(Seed));
|
||||
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
||||
if (hash_seed == sha256sum[0]) {
|
||||
memcpy(seed, lseed, 16);
|
||||
free(buffer);
|
||||
return 0; // found!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// out of options -> failed!
|
||||
free(buffer);
|
||||
return 1;
|
||||
}
|
||||
|
||||
u32 AddSeedToDb(SeedInfo* seed_info, SeedInfoEntry* seed_entry) {
|
||||
if (!seed_entry) { // no seed entry -> reset database
|
||||
memset(seed_info, 0, 16);
|
||||
return 0;
|
||||
}
|
||||
// check if entry already in DB
|
||||
u32 n_entries = seed_info->n_entries;
|
||||
SeedInfoEntry* seed = seed_info->entries;
|
||||
for (u32 i = 0; i < n_entries; i++, seed++)
|
||||
if (seed->titleId == seed_entry->titleId) return 0;
|
||||
// actually a new seed entry
|
||||
memcpy(seed, seed_entry, sizeof(SeedInfoEntry));
|
||||
seed_info->n_entries++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 InstallSeedDbToSystem(SeedInfo* seed_info, bool to_emunand) {
|
||||
char path[128];
|
||||
SeedDb* seeddb = (SeedDb*) malloc(sizeof(SeedDb));
|
||||
if (!seeddb) return 1;
|
||||
|
||||
// read the current SEEDDB database
|
||||
if ((GetSeedPath(path, to_emunand ? "4:" : "1:") != 0) ||
|
||||
(ReadDisaDiffIvfcLvl4(path, NULL, SEEDSAVE_AREA_OFFSET, sizeof(SeedDb), seeddb) != sizeof(SeedDb)) ||
|
||||
(seeddb->n_entries >= SEEDSAVE_MAX_ENTRIES)) {
|
||||
free (seeddb);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// find free slots, insert seeds from SeedInfo
|
||||
for (u32 slot = 0, s = 0; s < seed_info->n_entries; s++) {
|
||||
SeedInfoEntry* entry = &(seed_info->entries[s]);
|
||||
for (slot = 0; slot < seeddb->n_entries; slot++)
|
||||
if (seeddb->titleId[slot] == entry->titleId) break;
|
||||
if (slot >= SEEDSAVE_MAX_ENTRIES) break;
|
||||
if (slot >= seeddb->n_entries) seeddb->n_entries = slot + 1;
|
||||
seeddb->titleId[slot] = entry->titleId;
|
||||
memcpy(&(seeddb->seed[slot]), &(entry->seed), sizeof(Seed));
|
||||
}
|
||||
|
||||
// write back to system (warning: no write protection checks here)
|
||||
u32 size = WriteDisaDiffIvfcLvl4(path, NULL, SEEDSAVE_AREA_OFFSET, sizeof(SeedDb), seeddb);
|
||||
FixFileCmac(path, false);
|
||||
|
||||
free (seeddb);
|
||||
return (size == sizeof(SeedDb)) ? 0 : 1;
|
||||
}
|
||||
|
||||
u32 SetupSeedPrePurchase(u64 titleId, bool to_emunand) {
|
||||
// here, we ask the system to install the seed for us
|
||||
TitleTag* titletag = (TitleTag*) malloc(sizeof(TitleTag));
|
||||
if (!titletag) return 1;
|
||||
|
||||
char path[128];
|
||||
if ((GetSeedPath(path, to_emunand ? "4:" : "1:") != 0) ||
|
||||
(ReadDisaDiffIvfcLvl4(path, NULL, TITLETAG_AREA_OFFSET, sizeof(TitleTag), titletag) != sizeof(TitleTag)) ||
|
||||
(titletag->n_entries >= TITLETAG_MAX_ENTRIES)) {
|
||||
free (titletag);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// pointers for TITLETAG title IDs and seeds
|
||||
// find a free slot, insert titletag
|
||||
u32 slot = 0;
|
||||
for (; slot < titletag->n_entries; slot++)
|
||||
if (titletag->titleId[slot] == titleId) break;
|
||||
if (slot >= titletag->n_entries)
|
||||
titletag->n_entries = slot + 1;
|
||||
|
||||
TitleTagEntry* ttag = &(titletag->tag[slot]);
|
||||
titletag->titleId[slot] = titleId;
|
||||
memset(ttag, 0, sizeof(TitleTagEntry));
|
||||
memcpy(ttag->magic, "PREP", 4);
|
||||
ttag->year = 2000;
|
||||
ttag->month = 1;
|
||||
ttag->day = 1;
|
||||
ttag->country_code = 1;
|
||||
|
||||
// write back to system (warning: no write protection checks here)
|
||||
u32 size = WriteDisaDiffIvfcLvl4(path, NULL, TITLETAG_AREA_OFFSET, sizeof(TitleTag), titletag);
|
||||
FixFileCmac(path, false);
|
||||
|
||||
free(titletag);
|
||||
return (size == sizeof(TitleTag)) ? 0 : 1;
|
||||
}
|
||||
|
||||
u32 SetupSeedSystemCrypto(u64 titleId, u32 hash_seed, bool to_emunand) {
|
||||
// attempt to find the seed inside the seeddb.bin support file
|
||||
SeedInfo* seeddb = (SeedInfo*) malloc(STD_BUFFER_SIZE);
|
||||
if (!seeddb) return 1;
|
||||
|
||||
size_t len = LoadSupportFile(SEEDINFO_NAME, seeddb, STD_BUFFER_SIZE);
|
||||
if (len && (seeddb->n_entries <= (len - 16) / 32)) { // check filesize / seeddb size
|
||||
for (u32 s = 0; s < seeddb->n_entries; s++) {
|
||||
if (titleId != seeddb->entries[s].titleId)
|
||||
continue;
|
||||
// found a candidate, hash and verify it
|
||||
u8 lseed[16+8] __attribute__((aligned(4))) = { 0 }; // seed plus title ID for easy validation
|
||||
u32 sha256sum[8];
|
||||
memcpy(lseed+16, &titleId, 8);
|
||||
memcpy(lseed, &(seeddb->entries[s].seed), sizeof(Seed));
|
||||
sha_quick(sha256sum, lseed, 16 + 8, SHA256_MODE);
|
||||
u32 res = 0; // assuming the installed seed to be correct
|
||||
if (hash_seed == sha256sum[0]) {
|
||||
// found, install it
|
||||
seeddb->n_entries = 1;
|
||||
seeddb->entries[0].titleId = titleId;
|
||||
memcpy(&(seeddb->entries[0].seed), lseed, sizeof(Seed));
|
||||
res = InstallSeedDbToSystem(seeddb, to_emunand);
|
||||
}
|
||||
free(seeddb);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
free(seeddb);
|
||||
return 1;
|
||||
}
|
41
arm9/source/game/seedsave.h
Normal file
41
arm9/source/game/seedsave.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "disadiff.h"
|
||||
|
||||
#define SEEDINFO_NAME "seeddb.bin"
|
||||
#define SEEDINFO_SIZE(sdb) (16 + ((sdb)->n_entries * sizeof(SeedInfoEntry)))
|
||||
|
||||
#define SEEDSAVE_MAX_ENTRIES 2000
|
||||
#define SEEDSAVE_AREA_OFFSET 0x3000
|
||||
|
||||
typedef struct {
|
||||
u8 byte[16];
|
||||
} PACKED_STRUCT Seed;
|
||||
|
||||
typedef struct {
|
||||
u64 titleId;
|
||||
Seed seed;
|
||||
u8 reserved[8];
|
||||
} PACKED_STRUCT SeedInfoEntry;
|
||||
|
||||
typedef struct {
|
||||
u32 n_entries;
|
||||
u8 padding[12];
|
||||
SeedInfoEntry entries[SEEDSAVE_MAX_ENTRIES]; // this number is only a placeholder
|
||||
} PACKED_STRUCT SeedInfo;
|
||||
|
||||
typedef struct {
|
||||
u32 unknown0;
|
||||
u32 n_entries;
|
||||
u8 unknown1[0x1000 - 0x8];
|
||||
u64 titleId[SEEDSAVE_MAX_ENTRIES];
|
||||
Seed seed[SEEDSAVE_MAX_ENTRIES];
|
||||
} PACKED_STRUCT SeedDb;
|
||||
|
||||
u32 GetSeedPath(char* path, const char* drv);
|
||||
u32 FindSeed(u8* seed, u64 titleId, u32 hash_seed);
|
||||
u32 AddSeedToDb(SeedInfo* seed_info, SeedInfoEntry* seed_entry);
|
||||
u32 InstallSeedDbToSystem(SeedInfo* seed_info, bool to_emunand);
|
||||
u32 SetupSeedPrePurchase(u64 titleId, bool to_emunand);
|
||||
u32 SetupSeedSystemCrypto(u64 titleId, u32 hash_seed, bool to_emunand);
|
@ -1,16 +1,11 @@
|
||||
#include "ticketdb.h"
|
||||
#include "vbdri.h"
|
||||
#include "bdri.h"
|
||||
#include "support.h"
|
||||
#include "aes.h"
|
||||
#include "vff.h"
|
||||
#include "fsinit.h"
|
||||
#include "image.h"
|
||||
|
||||
const char* virtual_tickdb_dirs[] = {
|
||||
"homebrew",
|
||||
"eshop",
|
||||
"system",
|
||||
"unknown",
|
||||
};
|
||||
#define PART_PATH "D:/partitionA.bin"
|
||||
|
||||
u32 CryptTitleKey(TitleKeyEntry* tik, bool encrypt, bool devkit) {
|
||||
// From https://github.com/profi200/Project_CTR/blob/master/makerom/pki/prod.h#L19
|
||||
@ -61,54 +56,34 @@ u32 GetTitleKey(u8* titlekey, Ticket* ticket) {
|
||||
|
||||
u32 FindTicket(Ticket** ticket, u8* title_id, bool force_legit, bool emunand) {
|
||||
const char* path_db = TICKDB_PATH(emunand); // EmuNAND / SysNAND
|
||||
char path_store[256] = { 0 };
|
||||
char* path_bak = NULL;
|
||||
|
||||
// just to be safe
|
||||
*ticket = NULL;
|
||||
|
||||
// store previous mount path
|
||||
strncpy(path_store, GetMountPath(), 256);
|
||||
if (*path_store) path_bak = path_store;
|
||||
if (!InitImgFS(path_db))
|
||||
return 1;
|
||||
|
||||
char tid_string[17];
|
||||
u64 tid = getbe64(title_id);
|
||||
snprintf(tid_string, 17, "%016llX", tid);
|
||||
|
||||
DIR dir;
|
||||
FILINFO fno;
|
||||
char dir_path[12];
|
||||
char tik_path[64];
|
||||
|
||||
for (u32 i = force_legit ? 1 : 0; i < 4; i++) {
|
||||
snprintf(dir_path, 12, "T:/%s", virtual_tickdb_dirs[i]);
|
||||
|
||||
if (fvx_opendir(&dir, dir_path) != FR_OK) {
|
||||
InitImgFS(NULL);
|
||||
// search ticket in database
|
||||
if (ReadTicketFromDB(PART_PATH, title_id, ticket) != 0) {
|
||||
InitImgFS(path_bak);
|
||||
return 1;
|
||||
}
|
||||
|
||||
while ((fvx_readdir(&dir, &fno) == FR_OK) && *(fno.fname)) {
|
||||
if (strncmp(tid_string, fno.fname, 16) == 0) {
|
||||
snprintf(tik_path, 64, "%s/%s", dir_path, fno.fname);
|
||||
|
||||
u32 size = fvx_qsize(tik_path);
|
||||
if (!(*ticket = malloc(size))) {
|
||||
InitImgFS(NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((fvx_qread(tik_path, *ticket, 0, size, NULL) != FR_OK) ||
|
||||
(force_legit && (ValidateTicketSignature(*ticket) != 0))) {
|
||||
// (optional) validate ticket signature
|
||||
if (force_legit && (ValidateTicketSignature(*ticket) != 0)) {
|
||||
free(*ticket);
|
||||
InitImgFS(NULL);
|
||||
*ticket = NULL;
|
||||
InitImgFS(path_bak);
|
||||
return 1;
|
||||
}
|
||||
|
||||
InitImgFS(NULL);
|
||||
InitImgFS(path_bak);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
fvx_closedir(&dir);
|
||||
}
|
||||
|
||||
InitImgFS(NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
u32 FindTitleKey(Ticket* ticket, u8* title_id) {
|
||||
|
@ -6,7 +6,8 @@
|
||||
|
||||
u32 BuildTitleInfoEntryTmd(TitleInfoEntry* tie, TitleMetaData* tmd, bool sd) {
|
||||
u64 title_id = getbe64(tmd->title_id);
|
||||
u32 has_idx1 = false;
|
||||
bool has_idx1 = false;
|
||||
bool has_idx2 = false;
|
||||
|
||||
// set basic values
|
||||
memset(tie, 0x00, sizeof(TitleInfoEntry));
|
||||
@ -21,19 +22,32 @@ u32 BuildTitleInfoEntryTmd(TitleInfoEntry* tie, TitleMetaData* tmd, bool sd) {
|
||||
// align size: 0x4000 for TWL and CTRNAND, 0x8000 for SD
|
||||
u32 align_size = CMD_SIZE_ALIGN(sd);
|
||||
u32 content_count = getbe16(tmd->content_count);
|
||||
TmdContentChunk* chunk = (TmdContentChunk*) (tmd + 1);
|
||||
tie->title_size =
|
||||
(align_size * 3) + // base folder + 'content' + 'cmd'
|
||||
align(TMD_SIZE_N(content_count), align_size) + // TMD
|
||||
align_size; // CMD, placeholder (!!!)
|
||||
if (getle32(tmd->save_size) || getle32(tmd->twl_privsave_size) || (tmd->twl_flag & 0x2)) {
|
||||
tie->title_size +=
|
||||
align_size + // data folder
|
||||
align(getle32(tmd->save_size), align_size) +
|
||||
align(getle32(tmd->twl_privsave_size), align_size) +
|
||||
((tmd->twl_flag & 0x2) ? align(sizeof(TwlIconData), align_size) : 0);
|
||||
}
|
||||
|
||||
// contents title size + some additional stuff
|
||||
TmdContentChunk* chunk = (TmdContentChunk*) (tmd + 1);
|
||||
tie->content0_id = getbe32(chunk->id);
|
||||
for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++, chunk++) {
|
||||
if (getbe16(chunk->index) == 1) has_idx1 = true; // will be useful later
|
||||
else if (getbe16(chunk->index) == 2) has_idx2 = true; // will be useful later
|
||||
tie->title_size += align(getbe64(chunk->size), align_size);
|
||||
}
|
||||
|
||||
// manual? (we need to properly check this later)
|
||||
if (has_idx1 && (((title_id >> 32) == 0x00040000) || ((title_id >> 32) == 0x00040010))) {
|
||||
tie->flags_0[0] = 0x1; // this may have a manual
|
||||
// manual? dlp? save? (we need to properly check this later)
|
||||
if (((title_id >> 32) == 0x00040000) || ((title_id >> 32) == 0x00040010)) {
|
||||
if (has_idx1) tie->flags_0[0] = 0x1; // this may have a manual
|
||||
if (has_idx2) tie->title_version |= (0xFFFF << 16); // this may have a dlp
|
||||
if (getle32(tmd->save_size)) tie->flags_1[0] = 0x01; // this may have an sd save
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -59,7 +73,7 @@ u32 BuildTitleInfoEntryTwl(TitleInfoEntry* tie, TitleMetaData* tmd, TwlHeader* t
|
||||
tie->flags_2[0] = 0x01;
|
||||
tie->flags_2[4] = 0x01;
|
||||
tie->flags_2[5] = 0x01;
|
||||
}
|
||||
} else tie->content0_id = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -72,7 +86,10 @@ u32 BuildTitleInfoEntryNcch(TitleInfoEntry* tie, TitleMetaData* tmd, NcchHeader*
|
||||
|
||||
// product code, extended title version
|
||||
memcpy(tie->product_code, ncch->productcode, 0x10);
|
||||
tie->title_version |= (ncch->version << 16);
|
||||
tie->title_version &= ((ncch->version << 16) | 0xFFFF);
|
||||
|
||||
// NCCH titles need no content0 ID
|
||||
tie->content0_id = 0;
|
||||
|
||||
// specific flags
|
||||
// see: http://3dbrew.org/wiki/Titles
|
||||
@ -81,17 +98,13 @@ u32 BuildTitleInfoEntryNcch(TitleInfoEntry* tie, TitleMetaData* tmd, NcchHeader*
|
||||
|
||||
// stuff from extheader
|
||||
if (exthdr) {
|
||||
// add save data size to title size
|
||||
if (exthdr->savedata_size) {
|
||||
u32 align_size = CMD_SIZE_ALIGN(sd);
|
||||
tie->title_size +=
|
||||
align_size + // 'data' folder
|
||||
align(exthdr->savedata_size, align_size); // savegame
|
||||
tie->flags_1[0] = 0x01; // has SD save
|
||||
};
|
||||
// extdata ID low (hacky)
|
||||
tie->extdata_id_low = getle32(exthdr->aci_data + 0x30 - 0x0C + 0x04);
|
||||
} else tie->flags_0[0] = 0x00; // no manual
|
||||
// extdata ID low (hacky, we navigate to storage info)
|
||||
tie->extdata_id_low = getle32(exthdr->aci_data + (0x30 - 0x0C));
|
||||
} else {
|
||||
tie->flags_0[0] = 0x00; // no manual
|
||||
tie->flags_1[0] = 0x00; // no sd save
|
||||
tie->title_version &= 0xFFFF; // no dlp
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -22,7 +22,8 @@ typedef struct {
|
||||
u8 reserved1[4];
|
||||
u8 flags_2[8];
|
||||
char product_code[16];
|
||||
u8 reserved2[16];
|
||||
u8 reserved2[12];
|
||||
u32 content0_id; // only relevant for TWL?
|
||||
u8 unknown[4]; // appears to not matter what's here
|
||||
u8 reserved3[44];
|
||||
} __attribute__((packed)) TitleInfoEntry;
|
||||
|
@ -76,7 +76,7 @@ u32 FixTmdHashes(TitleMetaData* tmd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size, u32 twl_privsave_size) {
|
||||
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size, u32 twl_privsave_size, u8 twl_flag) {
|
||||
static const u8 sig_type[4] = { TMD_SIG_TYPE };
|
||||
// safety check: number of contents
|
||||
if (n_contents > TMD_MAX_CONTENTS) return 1; // potential incompatibility here (!)
|
||||
@ -91,6 +91,7 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size
|
||||
tmd->title_type[3] = 0x40; // whatever
|
||||
for (u32 i = 0; i < 4; i++) tmd->save_size[i] = (save_size >> (i*8)) & 0xFF; // le save size
|
||||
for (u32 i = 0; i < 4; i++) tmd->twl_privsave_size[i] = (twl_privsave_size >> (i*8)) & 0xFF; // le privsave size
|
||||
tmd->twl_flag = twl_flag;
|
||||
tmd->content_count[0] = (u8) ((n_contents >> 8) & 0xFF);
|
||||
tmd->content_count[1] = (u8) (n_contents & 0xFF);
|
||||
memset(tmd->contentinfo_hash, 0xFF, 0x20); // placeholder (hash)
|
||||
|
@ -62,5 +62,5 @@ u32 ValidateTmdSignature(TitleMetaData* tmd);
|
||||
u32 VerifyTmd(TitleMetaData* tmd);
|
||||
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk);
|
||||
u32 FixTmdHashes(TitleMetaData* tmd);
|
||||
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size, u32 twl_privsave_size);
|
||||
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size, u32 twl_privsave_size, u8 twl_flag);
|
||||
u32 BuildTmdCert(u8* tmdcert);
|
||||
|
@ -109,6 +109,7 @@ u32 BootFirmHandler(const char* bootpath, bool verbose, bool delete) {
|
||||
DeinitExtFS();
|
||||
DeinitSDCardFS();
|
||||
PXI_DoCMD(PXICMD_LEGACY_BOOT, NULL, 0);
|
||||
PXI_Barrier(PXI_FIRMLAUNCH_BARRIER);
|
||||
BootFirm((FirmHeader*) firm, fixpath);
|
||||
while(1);
|
||||
}
|
||||
@ -689,7 +690,7 @@ u32 FileHexViewer(const char* path) {
|
||||
marked0 = (s32) found_offset - (offset + curr_pos);
|
||||
marked1 = marked0 + found_size;
|
||||
if (marked0 < 0) marked0 = 0;
|
||||
if (marked1 > cols) marked1 = cols;
|
||||
if (marked1 > (s32) cols) marked1 = (s32) cols;
|
||||
}
|
||||
|
||||
// switch to bottom screen
|
||||
@ -708,7 +709,7 @@ u32 FileHexViewer(const char* path) {
|
||||
COLOR_STD_BG, "%08X", (unsigned int) offset + curr_pos);
|
||||
if (x_ascii >= 0) {
|
||||
DrawString(screen, ascii, x_ascii - x0, y, COLOR_HVASCII, COLOR_STD_BG, false);
|
||||
for (u32 i = marked0; i < marked1; i++)
|
||||
for (u32 i = (u32) marked0; i < (u32) marked1; i++)
|
||||
DrawCharacter(screen, ascii[i % cols], x_ascii - x0 + (FONT_WIDTH_EXT * i), y, COLOR_MARKED, COLOR_STD_BG);
|
||||
if (edit_mode && ((u32) cursor / cols == row)) DrawCharacter(screen, ascii[cursor % cols],
|
||||
x_ascii - x0 + FONT_WIDTH_EXT * (cursor % cols), y, COLOR_RED, COLOR_STD_BG);
|
||||
@ -718,7 +719,7 @@ u32 FileHexViewer(const char* path) {
|
||||
for (u32 col = 0; (col < cols) && (x_hex >= 0); col++) {
|
||||
u32 x = (x_hex + hlpad) + (((2*FONT_WIDTH_EXT) + hrpad + hlpad) * col) - x0;
|
||||
u32 hex_color = (edit_mode && ((u32) cursor == curr_pos + col)) ? COLOR_RED :
|
||||
((col >= marked0) && (col < marked1)) ? COLOR_MARKED : COLOR_HVHEX(col);
|
||||
(((s32) col >= marked0) && ((s32) col < marked1)) ? COLOR_MARKED : COLOR_HVHEX(col);
|
||||
if (col < cutoff)
|
||||
DrawStringF(screen, x, y, hex_color, COLOR_STD_BG, "%02X", (unsigned int) data[curr_pos + col]);
|
||||
else DrawStringF(screen, x, y, hex_color, COLOR_STD_BG, " ");
|
||||
@ -962,7 +963,7 @@ u32 DirFileAttrMenu(const char* path, const char *name) {
|
||||
if (fvx_stat(path, &fno) != FR_OK) return 1;
|
||||
vrt = (fno.fattrib & AM_VRT);
|
||||
new_attrib = fno.fattrib;
|
||||
snprintf(datestr, 64, "%s: %04d-%02d-%02d %02d:%02d:%02d\n",
|
||||
snprintf(datestr, 32, "%s: %04d-%02d-%02d %02d:%02d:%02d\n",
|
||||
(fno.fattrib & AM_DIR) ? "created" : "modified",
|
||||
1980 + ((fno.fdate >> 9) & 0x7F), (fno.fdate >> 5) & 0xF, fno.fdate & 0x1F,
|
||||
(fno.ftime >> 11) & 0x1F, (fno.ftime >> 5) & 0x3F, (fno.ftime & 0x1F) << 1);
|
||||
@ -1091,7 +1092,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
bool cia_buildable = (FTYPE_CIABUILD(filetype));
|
||||
bool cia_buildable_legit = (FTYPE_CIABUILD_L(filetype));
|
||||
bool cia_installable = (FTYPE_CIAINSTALL(filetype)) && !(drvtype & DRV_CTRNAND) &&
|
||||
!(drvtype & DRV_TWLNAND) && !(drvtype & DRV_ALIAS);
|
||||
!(drvtype & DRV_TWLNAND) && !(drvtype & DRV_ALIAS) && !(drvtype & DRV_IMAGE);
|
||||
bool uninstallable = (FTYPE_UNINSTALL(filetype));
|
||||
bool cxi_dumpable = (FTYPE_CXIDUMP(filetype));
|
||||
bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path;
|
||||
bool key_buildable = (FTYPE_KEYBUILD(filetype)) && !in_output_path &&
|
||||
@ -1154,6 +1156,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
(strncmp(clipboard->entry[0].path, file_path, 256) != 0)) ?
|
||||
(int) ++n_opt : -1;
|
||||
int searchdrv = (DriveType(current_path) & DRV_SEARCH) ? ++n_opt : -1;
|
||||
int titleman = (filetype & GAME_TIE) ? ++n_opt : -1;
|
||||
if (special > 0) optionstr[special-1] =
|
||||
(filetype & IMG_NAND ) ? "NAND image options..." :
|
||||
(filetype & IMG_FAT ) ? (transferable) ? "CTRNAND options..." : "Mount as FAT image" :
|
||||
@ -1163,6 +1166,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
(filetype & GAME_EXEFS) ? "Mount as EXEFS image" :
|
||||
(filetype & GAME_ROMFS) ? "Mount as ROMFS image" :
|
||||
(filetype & GAME_TMD ) ? "TMD file options..." :
|
||||
(filetype & GAME_TIE ) ? "Manage Title..." :
|
||||
(filetype & GAME_BOSS ) ? "BOSS file options..." :
|
||||
(filetype & GAME_NUSCDN)? "Decrypt NUS/CDN file" :
|
||||
(filetype & GAME_SMDH) ? "Show SMDH title info" :
|
||||
@ -1193,6 +1197,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
if (copystd > 0) optionstr[copystd-1] = "Copy to " OUTPUT_PATH;
|
||||
if (inject > 0) optionstr[inject-1] = "Inject data @offset";
|
||||
if (searchdrv > 0) optionstr[searchdrv-1] = "Open containing folder";
|
||||
if (titleman > 0) optionstr[titleman-1] = "Open title folder";
|
||||
|
||||
int user_select = ShowSelectPrompt(n_opt, optionstr, (n_marked > 1) ?
|
||||
"%s\n%(%lu files selected)" : "%s", pathstr, n_marked);
|
||||
@ -1273,8 +1278,12 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else if (user_select == searchdrv) { // -> search drive, open containing path
|
||||
char* last_slash = strrchr(file_path, '/');
|
||||
else if ((user_select == searchdrv) || (user_select == titleman)) { // -> open containing path
|
||||
char temp_path[256];
|
||||
if (user_select == searchdrv) strncpy(temp_path, file_path, 256);
|
||||
else if (GetTieContentPath(temp_path, file_path) != 0) return 0;
|
||||
|
||||
char* last_slash = strrchr(temp_path, '/');
|
||||
if (last_slash) {
|
||||
if (N_PANES) { // switch to next pane
|
||||
memcpy((*pane)->path, current_path, 256); // store current pane state
|
||||
@ -1282,12 +1291,12 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
(*pane)->scroll = *scroll;
|
||||
if (++*pane >= panedata + N_PANES) *pane -= N_PANES;
|
||||
}
|
||||
snprintf(current_path, last_slash - file_path + 1, "%s", file_path);
|
||||
snprintf(current_path, last_slash - temp_path + 1, "%s", temp_path);
|
||||
GetDirContents(current_dir, current_path);
|
||||
*scroll = 0;
|
||||
for (*cursor = 1; *cursor < current_dir->n_entries; (*cursor)++) {
|
||||
DirEntry* entry = &(current_dir->entry[*cursor]);
|
||||
if (strncasecmp(entry->path, file_path, 256) == 0) break;
|
||||
if (strncasecmp(entry->path, temp_path, 256) == 0) break;
|
||||
}
|
||||
if (*cursor >= current_dir->n_entries)
|
||||
*cursor = 1;
|
||||
@ -1312,6 +1321,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
int cia_build_legit = (cia_buildable_legit) ? ++n_opt : -1;
|
||||
int cxi_dump = (cxi_dumpable) ? ++n_opt : -1;
|
||||
int cia_install = (cia_installable) ? ++n_opt : -1;
|
||||
int uninstall = (uninstallable) ? ++n_opt : -1;
|
||||
int tik_build_enc = (tik_buildable) ? ++n_opt : -1;
|
||||
int tik_build_dec = (tik_buildable) ? ++n_opt : -1;
|
||||
int key_build = (key_buildable) ? ++n_opt : -1;
|
||||
@ -1344,7 +1354,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
if (cia_build > 0) optionstr[cia_build-1] = (cia_build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)";
|
||||
if (cia_build_legit > 0) optionstr[cia_build_legit-1] = "Build CIA (legit)";
|
||||
if (cxi_dump > 0) optionstr[cxi_dump-1] = "Dump CXI/NDS file";
|
||||
if (cia_install > 0) optionstr[cia_install-1] = "Install game file";
|
||||
if (cia_install > 0) optionstr[cia_install-1] = "Install game image";
|
||||
if (uninstall > 0) optionstr[uninstall-1] = "Uninstall title";
|
||||
if (tik_build_enc > 0) optionstr[tik_build_enc-1] = "Build " TIKDB_NAME_ENC;
|
||||
if (tik_build_dec > 0) optionstr[tik_build_dec-1] = "Build " TIKDB_NAME_DEC;
|
||||
if (key_build > 0) optionstr[key_build-1] = "Build " KEYDB_NAME;
|
||||
@ -1598,6 +1609,44 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else if (user_select == uninstall) { // -> uninstall title
|
||||
bool full_uninstall = false;
|
||||
|
||||
// safety confirmation
|
||||
optionstr[0] = "Keep ticket & savegame";
|
||||
optionstr[1] = "Uninstall everything";
|
||||
optionstr[2] = "Abort uninstall";
|
||||
user_select = (int) (n_marked > 1) ?
|
||||
ShowSelectPrompt(3, optionstr, "Uninstall %lu selected titles?", n_marked) :
|
||||
ShowSelectPrompt(3, optionstr, "%s\nUninstall selected title?", pathstr);
|
||||
full_uninstall = (user_select == 2);
|
||||
if (!user_select || (user_select == 3))
|
||||
return 0;
|
||||
|
||||
// batch uninstall
|
||||
if (n_marked > 1) {
|
||||
u32 n_success = 0;
|
||||
u32 num = 0;
|
||||
ShowProgress(0, 0, "batch uninstall");
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
||||
const char* path = current_dir->entry[i].path;
|
||||
if (!current_dir->entry[i].marked) continue;
|
||||
if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) continue;
|
||||
if (!ShowProgress(++num, n_marked, path)) break;
|
||||
if (UninstallGameDataTie(path, true, full_uninstall, full_uninstall) == 0)
|
||||
n_success++;
|
||||
}
|
||||
ShowPrompt(false, "%lu/%lu titles uninstalled", n_success, n_marked);
|
||||
} else {
|
||||
ShowString("%s\nUninstalling, please wait...", pathstr);
|
||||
if (UninstallGameDataTie(file_path, true, full_uninstall, full_uninstall) != 0)
|
||||
ShowPrompt(false, "%s\nUninstall failed!", pathstr);
|
||||
ClearScreenF(true, false, COLOR_STD_BG);
|
||||
}
|
||||
|
||||
GetDirContents(current_dir, current_path);
|
||||
return 0;
|
||||
}
|
||||
else if (user_select == verify) { // -> verify game / nand file
|
||||
if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) {
|
||||
u32 n_success = 0;
|
||||
@ -2062,7 +2111,7 @@ u32 HomeMoreMenu(char* current_path) {
|
||||
bool seed_sys = false;
|
||||
bool seed_emu = false;
|
||||
if (BuildSeedInfo(NULL, false) == 0) {
|
||||
ShowString("Building " SEEDDB_NAME "...");
|
||||
ShowString("Building " SEEDINFO_NAME "...");
|
||||
seed_sys = (BuildSeedInfo("1:", false) == 0);
|
||||
seed_emu = (BuildSeedInfo("4:", false) == 0);
|
||||
if (!seed_sys || BuildSeedInfo(NULL, true) != 0)
|
||||
@ -2071,7 +2120,7 @@ u32 HomeMoreMenu(char* current_path) {
|
||||
ShowPrompt(false, "Built in " OUTPUT_PATH ":\n \n%18.18-s %s\n%18.18-s %s\n%18.18-s %s",
|
||||
TIKDB_NAME_ENC, tik_enc_sys ? tik_enc_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed",
|
||||
TIKDB_NAME_DEC, tik_dec_sys ? tik_dec_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed",
|
||||
SEEDDB_NAME, seed_sys ? seed_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed");
|
||||
SEEDINFO_NAME, seed_sys ? seed_emu ? "OK (Sys&Emu)" : "OK (Sys)" : "Failed");
|
||||
GetDirContents(current_dir, current_path);
|
||||
return 0;
|
||||
}
|
||||
@ -2271,7 +2320,10 @@ u32 GodMode(int entrypoint) {
|
||||
// bootloader handler
|
||||
if (bootloader) {
|
||||
const char* bootfirm_paths[] = { BOOTFIRM_PATHS };
|
||||
if (IsBootableFirm(firm_in_mem, FIRM_MAX_SIZE)) BootFirm(firm_in_mem, "sdmc:/bootonce.firm");
|
||||
if (IsBootableFirm(firm_in_mem, FIRM_MAX_SIZE)) {
|
||||
PXI_Barrier(PXI_FIRMLAUNCH_BARRIER);
|
||||
BootFirm(firm_in_mem, "sdmc:/bootonce.firm");
|
||||
}
|
||||
for (u32 i = 0; i < sizeof(bootfirm_paths) / sizeof(char*); i++) {
|
||||
BootFirmHandler(bootfirm_paths[i], false, (BOOTFIRM_TEMPS >> i) & 0x1);
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ void main(int argc, char** argv, int entrypoint)
|
||||
|
||||
// Don't even try to send any messages until the
|
||||
// ARM11 says it's ready
|
||||
PXI_Barrier(ARM11_READY_BARRIER);
|
||||
PXI_Barrier(PXI_BOOT_BARRIER);
|
||||
|
||||
// A pointer to the shared memory region is
|
||||
// stored in the thread ID register in the ARM9
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "disadiff.h"
|
||||
#include "game.h"
|
||||
#include "nand.h" // so that we can trim NAND images
|
||||
#include "itcm.h" // we need access to part of the OTP
|
||||
#include "hid.h"
|
||||
#include "ui.h"
|
||||
#include "fs.h"
|
||||
@ -14,6 +15,9 @@
|
||||
#define CRYPTO_DECRYPT NCCH_NOCRYPTO
|
||||
#define CRYPTO_ENCRYPT NCCH_STDCRYPTO
|
||||
|
||||
// partitionA path
|
||||
#define PART_PATH "D:/partitionA.bin"
|
||||
|
||||
u32 GetNcchHeaders(NcchHeader* ncch, NcchExtHeader* exthdr, ExeFsHeader* exefs, FIL* file, bool nocrypto) {
|
||||
u32 offset_ncch = fvx_tell(file);
|
||||
UINT btr;
|
||||
@ -260,6 +264,56 @@ u32 GetTmdContentPath(char* path_content, const char* path_tmd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 GetTieTmdPath(char* path_tmd, const char* path_tie) {
|
||||
char drv[3] = { 0x00 };
|
||||
u64 tid64 = 0;
|
||||
|
||||
// this relies on:
|
||||
// 1: titleinfo entries are only loaded from mounted [1/4/A/B]:/dbs/title.db
|
||||
// 2: filename starts with title id
|
||||
|
||||
// basic sanity check
|
||||
if (*path_tie != 'T') return 1;
|
||||
|
||||
// get title id
|
||||
if (sscanf(path_tie, "T:/%016llx", &tid64) != 1) return 1;
|
||||
u32 tid_high = (u32) ((tid64 >> 32) & 0xFFFFFFFF);
|
||||
u32 tid_low = (u32) (tid64 & 0xFFFFFFFF);
|
||||
|
||||
// load TitleDB entry file
|
||||
TitleInfoEntry tie;
|
||||
if (fvx_qread(path_tie, &tie, 0, sizeof(TitleInfoEntry), NULL) != FR_OK)
|
||||
return 1;
|
||||
|
||||
// determine the drive
|
||||
const char* mntpath = GetMountPath();
|
||||
if (!mntpath || !*mntpath) return 1;
|
||||
strncpy(drv, mntpath, 2);
|
||||
if (tid_high & 0x8000) {
|
||||
if (*drv == '1') *drv = '2';
|
||||
if (*drv == '4') *drv = '5';
|
||||
tid_high = 0x00030000 | (tid_high&0xFF);
|
||||
}
|
||||
|
||||
// build the path
|
||||
snprintf(path_tmd, 64, "%2.2s/title/%08lX/%08lX/content/%08lx.tmd",
|
||||
drv, tid_high, tid_low, tie.tmd_content_id);
|
||||
|
||||
// done
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 GetTieContentPath(char* path_content, const char* path_tie) {
|
||||
char path_tmd[64];
|
||||
|
||||
// get the TMD path first
|
||||
if (GetTieTmdPath(path_tmd, path_tie) != 0)
|
||||
return 1;
|
||||
|
||||
// let the TMD content path function take over
|
||||
return GetTmdContentPath(path_content, path_tmd);
|
||||
}
|
||||
|
||||
u32 WriteCiaStub(CiaStub* stub, const char* path) {
|
||||
FIL file;
|
||||
UINT btw;
|
||||
@ -663,6 +717,17 @@ u32 VerifyTmdFile(const char* path, bool cdn) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 VerifyTieFile(const char* path) {
|
||||
char path_tmd[64];
|
||||
|
||||
// get the TMD path
|
||||
if (GetTieTmdPath(path_tmd, path) != 0)
|
||||
return 1;
|
||||
|
||||
// let the TMD verificator take over
|
||||
return VerifyTmdFile(path_tmd, false);
|
||||
}
|
||||
|
||||
u32 VerifyFirmFile(const char* path) {
|
||||
char pathstr[32 + 1];
|
||||
TruncateString(pathstr, path, 32, 8);
|
||||
@ -789,6 +854,8 @@ u32 VerifyGameFile(const char* path) {
|
||||
return VerifyNcchFile(path, 0, 0);
|
||||
else if (filetype & GAME_TMD)
|
||||
return VerifyTmdFile(path, filetype & FLAG_NUSCDN);
|
||||
else if (filetype & GAME_TIE)
|
||||
return VerifyTieFile(path);
|
||||
else if (filetype & GAME_BOSS)
|
||||
return VerifyBossFile(path);
|
||||
else if (filetype & SYS_FIRM)
|
||||
@ -1236,11 +1303,43 @@ u32 CryptGameFile(const char* path, bool inplace, bool encrypt) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32 GetInstallPath(char* path, const char* drv, const u8* title_id, const u8* content_id, const char* str) {
|
||||
u32 GetInstallDataDrive(char* drv, u64 tid64, bool to_emunand) {
|
||||
// check the title id
|
||||
bool to_twl = ((tid64 >> 32) & 0x8000);
|
||||
bool to_sd = (!to_twl && !((tid64 >> 32) & 0x10));
|
||||
|
||||
// determine the correct drive
|
||||
drv[0] = to_emunand ?
|
||||
(to_twl ? '5' : to_sd ? 'B' : '4') :
|
||||
(to_twl ? '2' : to_sd ? 'A' : '1');
|
||||
drv[1] = ':';
|
||||
drv[2] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 GetInstallDbsPath(char* path, const char* drv, const char* str) {
|
||||
bool is_ticketdb = (strncasecmp(str, "ticket.db", 10) == 0);
|
||||
|
||||
// fix the drive if required
|
||||
if (*drv == '2') drv = "1:";
|
||||
else if (*drv == '5') drv = "4:";
|
||||
else if (is_ticketdb) {
|
||||
if (*drv == 'A') drv = "1:";
|
||||
else if (*drv == 'B') drv = "4:";
|
||||
}
|
||||
|
||||
// build the path
|
||||
snprintf(path, 256, "%2.2s/dbs/%s", drv, str);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 GetInstallPath(char* path, const char* drv, u64 tid64, const u8* content_id, const char* str) {
|
||||
static const u8 dlc_tid_high[] = { DLC_TID_HIGH };
|
||||
bool dlc = (memcmp(title_id, dlc_tid_high, sizeof(dlc_tid_high)) == 0);
|
||||
u32 tid_high = getbe32(title_id);
|
||||
u32 tid_low = getbe32(title_id + 4);
|
||||
u32 tid_high = (u32) ((tid64 >> 32) & 0xFFFFFFFF);
|
||||
u32 tid_low = (u32) (tid64 & 0xFFFFFFFF);
|
||||
bool dlc = (tid_high == getbe32(dlc_tid_high));
|
||||
|
||||
if ((*drv == '2') || (*drv == '5')) // TWL titles need TWL title ID
|
||||
tid_high = 0x00030000 | (tid_high&0xFF);
|
||||
@ -1259,8 +1358,12 @@ u32 GetInstallPath(char* path, const char* drv, const u8* title_id, const u8* co
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 GetInstallSavePath(char* path, const char* drv, const u8* title_id) {
|
||||
u32 CreateSaveData(const char* drv, u64 tid64, const char* name, u32 save_size, bool overwrite) {
|
||||
bool is_twl = ((*drv == '2') || (*drv == '5'));
|
||||
char path_save[128];
|
||||
|
||||
// generate the save path (thanks ihaveamac for system path)
|
||||
// we use hardcoded names / numbers for CTR saves
|
||||
if ((*drv == '1') || (*drv == '4')) { // ooof, system save
|
||||
// get the id0
|
||||
u8 sd_keyy[16] __attribute__((aligned(4)));
|
||||
@ -1271,22 +1374,136 @@ u32 GetInstallSavePath(char* path, const char* drv, const u8* title_id) {
|
||||
memset(sd_keyy, 0x00, 16);
|
||||
sha_quick(sha256sum, sd_keyy, 0x10, SHA256_MODE);
|
||||
// build path
|
||||
u32 tid_low = getbe32(title_id + 4);
|
||||
snprintf(path, 128, "%2.2s/data/%08lx%08lx%08lx%08lx/sysdata/%08lx/00000000",
|
||||
u32 tid_low = (u32) (tid64 & 0xFFFFFFFF);
|
||||
snprintf(path_save, 128, "%2.2s/data/%08lx%08lx%08lx%08lx/sysdata/%08lx%s",
|
||||
drv, sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3],
|
||||
tid_low | 0x00020000);
|
||||
tid_low | 0x00020000, name ? "/00000000" : "");
|
||||
return 0;
|
||||
} else { // SD save, simple
|
||||
return GetInstallPath(path, drv, title_id, NULL, "data/00000001.sav");
|
||||
} else if (!is_twl || !name) { // SD CTR save or no name, simple
|
||||
GetInstallPath(path_save, drv, tid64, NULL, name ? "data/00000001.sav" : "data");
|
||||
} else {
|
||||
char substr[64];
|
||||
snprintf(substr, 64, "data/%s", name);
|
||||
GetInstallPath(path_save, drv, tid64, NULL, substr);
|
||||
}
|
||||
|
||||
// if name is NULL, we remove instead of create
|
||||
if (!name) {
|
||||
fvx_runlink(path_save);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// generate the save file, first check if it already exists
|
||||
if (overwrite || (fvx_qsize(path_save) != save_size)) {
|
||||
fvx_rmkpath(path_save);
|
||||
if (fvx_qcreate(path_save, save_size) != FR_OK) return 1;
|
||||
|
||||
if (!is_twl) { // CTR save, simple case
|
||||
static const u8 zeroes[0x20] = { 0x00 };
|
||||
if (fvx_qwrite(path_save, zeroes, 0, 0x20, NULL) != FR_OK)
|
||||
return 1;
|
||||
} else if ((strncmp(name, "public.sav", 11) == 0) || // fat12 image
|
||||
(strncmp(name, "private.sav", 12) == 0)) {
|
||||
u8* fat16k = (u8*) malloc(0x4000); // 16kiB, that's enough
|
||||
if (!fat16k) return 1;
|
||||
memset(fat16k, 0x00, 0x4000);
|
||||
|
||||
if ((BuildTwlSaveHeader(fat16k, save_size) != 0) ||
|
||||
(fvx_qwrite(path_save, fat16k, 0, min(save_size, 0x4000), NULL) != FR_OK)) {
|
||||
free(fat16k);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 UninstallGameData(u64 tid64, bool remove_tie, bool remove_ticket, bool remove_save, bool from_emunand) {
|
||||
char drv[3];
|
||||
|
||||
// check permissions for SysNAND (this includes everything we need)
|
||||
if (!CheckWritePermissions(from_emunand ? "4:" : "1:")) return 1;
|
||||
|
||||
// determine the drive
|
||||
if (GetInstallDataDrive(drv, tid64, from_emunand) != 0) return 1;
|
||||
|
||||
// remove data path
|
||||
char path_data[256];
|
||||
if (GetInstallPath(path_data, drv, tid64, NULL, remove_save ? NULL : "content") != 0) return 1;
|
||||
fvx_runlink(path_data);
|
||||
|
||||
// clear leftovers
|
||||
if (GetInstallPath(path_data, drv, tid64, NULL, NULL) != 0)
|
||||
fvx_unlink(path_data);
|
||||
|
||||
// rmeove save (additional step required for system titles)
|
||||
CreateSaveData(drv, tid64, NULL, 0, true);
|
||||
|
||||
// remove titledb entry / ticket
|
||||
u32 ret = 0;
|
||||
if (remove_tie || remove_ticket) {
|
||||
// ensure remounting the old mount path
|
||||
char path_store[256] = { 0 };
|
||||
char* path_bak = NULL;
|
||||
strncpy(path_store, GetMountPath(), 256);
|
||||
if (*path_store) path_bak = path_store;
|
||||
|
||||
// we need the big endian title ID
|
||||
u8 title_id[8];
|
||||
for (u32 i = 0; i < 8; i++)
|
||||
title_id[i] = (tid64 >> ((7-i)*8)) & 0xFF;
|
||||
|
||||
// ticket database
|
||||
if (remove_ticket) {
|
||||
char path_ticketdb[256];
|
||||
if ((GetInstallDbsPath(path_ticketdb, drv, "ticket.db") != 0) || !InitImgFS(path_ticketdb) ||
|
||||
((RemoveTicketFromDB(PART_PATH, title_id)) != 0)) ret = 1;
|
||||
}
|
||||
|
||||
// title database
|
||||
if (remove_tie) {
|
||||
char path_titledb[256];
|
||||
if ((GetInstallDbsPath(path_titledb, drv, "title.db") != 0) || !InitImgFS(path_titledb) ||
|
||||
((RemoveTitleInfoEntryFromDB(PART_PATH, title_id)) != 0)) ret = 1;
|
||||
}
|
||||
|
||||
// restore old mount path
|
||||
InitImgFS(path_bak);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32 UninstallGameDataTie(const char* path, bool remove_tie, bool remove_ticket, bool remove_save) {
|
||||
// requirements for this to work:
|
||||
// * title.db from standard path mounted to T:/
|
||||
// * entry filename starts with title id
|
||||
// * these two conditions need to be fulfilled for all ties
|
||||
bool from_emunand = false;
|
||||
u64 tid64;
|
||||
|
||||
const char* mntpath = GetMountPath();
|
||||
if (!mntpath) return 1;
|
||||
|
||||
// title.db from emunand?
|
||||
if ((strncasecmp(mntpath, "B:/dbs/title.db", 16) == 0) ||
|
||||
(strncasecmp(mntpath, "4:/dbs/title.db", 16) == 0))
|
||||
from_emunand = true;
|
||||
|
||||
// get title ID
|
||||
if (sscanf(path, "T:/%016llx", &tid64) != 1)
|
||||
return 1;
|
||||
|
||||
return UninstallGameData(tid64, remove_tie, remove_ticket, remove_save, from_emunand);
|
||||
}
|
||||
|
||||
u32 InstallCiaContent(const char* drv, const char* path_content, u32 offset, u32 size,
|
||||
TmdContentChunk* chunk, const u8* title_id, const u8* titlekey, bool cxi_fix) {
|
||||
TmdContentChunk* chunk, const u8* title_id, const u8* titlekey, bool cxi_fix, bool cdn_decrypt) {
|
||||
char dest[256];
|
||||
|
||||
// create destination path and ensure it exists
|
||||
GetInstallPath(dest, drv, title_id, chunk->id, NULL);
|
||||
GetInstallPath(dest, drv, getbe64(title_id), chunk->id, NULL);
|
||||
fvx_rmkpath(dest);
|
||||
|
||||
// open file(s)
|
||||
@ -1335,7 +1552,7 @@ u32 InstallCiaContent(const char* drv, const char* path_content, u32 offset, u32
|
||||
for (u32 i = 0; (i < size) && (ret == 0); i += STD_BUFFER_SIZE) {
|
||||
u32 read_bytes = min(STD_BUFFER_SIZE, (size - i));
|
||||
if (fvx_read(&ofile, buffer, read_bytes, &bytes_read) != FR_OK) ret = 1;
|
||||
if (cia_crypto && (DecryptCiaContentSequential(buffer, read_bytes, ctr_in, titlekey) != 0)) ret = 1;
|
||||
if ((cia_crypto || cdn_decrypt) && (DecryptCiaContentSequential(buffer, read_bytes, ctr_in, titlekey) != 0)) ret = 1;
|
||||
if ((i == 0) && cxi_fix && (SetNcchSdFlag(buffer) != 0)) ret = 1;
|
||||
if (i == 0) sha_init(SHA256_MODE);
|
||||
sha_update(buffer, read_bytes);
|
||||
@ -1370,10 +1587,12 @@ u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
|
||||
TmdContentChunk* content_list = cia->content_list;
|
||||
u32 content_count = getbe16(tmd->content_count);
|
||||
u8* title_id = ticket->title_id;
|
||||
u64 tid64 = getbe64(title_id);
|
||||
|
||||
bool sdtie = ((*drv == 'A') || (*drv == 'B'));
|
||||
bool syscmd = (((*drv == '1') || (*drv == '4')) ||
|
||||
(((*drv == '2') || (*drv == '5')) && (title_id[3] != 0x04)));
|
||||
bool to_emunand = ((*drv == 'B') || (*drv == '4') || (*drv == '5'));
|
||||
|
||||
char path_titledb[32];
|
||||
char path_ticketdb[32];
|
||||
@ -1386,15 +1605,12 @@ u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
|
||||
(*drv != '4') && (*drv != '5') && (*drv != 'B'))
|
||||
return 1;
|
||||
|
||||
// progress update
|
||||
if (!ShowProgress(0, 0, "TMD/CMD/TiE/Ticket/Save")) return 1;
|
||||
|
||||
// collect data for title info entry
|
||||
char path_cnt0[256];
|
||||
u8 hdr_cnt0[0x600]; // we don't need more
|
||||
NcchHeader* ncch = NULL;
|
||||
NcchExtHeader* exthdr = NULL;
|
||||
GetInstallPath(path_cnt0, drv, title_id, content_list->id, NULL);
|
||||
GetInstallPath(path_cnt0, drv, tid64, content_list->id, NULL);
|
||||
if (fvx_qread(path_cnt0, hdr_cnt0, 0, 0x600, NULL) != FR_OK)
|
||||
return 1;
|
||||
if (ValidateNcchHeader((void*) hdr_cnt0) == 0) {
|
||||
@ -1413,6 +1629,7 @@ u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
|
||||
// build the cmd
|
||||
cmd = BuildAllocCmdData(tmd);
|
||||
if (!cmd) return 1;
|
||||
if (!syscmd) cmd->unknown = 0xFFFFFFFE; // mark this as custom built
|
||||
|
||||
// generate all the paths
|
||||
snprintf(path_titledb, 32, "%2.2s/dbs/title.db",
|
||||
@ -1420,11 +1637,8 @@ u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
|
||||
snprintf(path_ticketdb, 32, "%2.2s/dbs/ticket.db",
|
||||
((*drv == 'A') || (*drv == '2')) ? "1:" :
|
||||
((*drv == 'B') || (*drv == '5')) ? "4:" : drv);
|
||||
GetInstallPath(path_tmd, drv, title_id, NULL, "content/00000000.tmd");
|
||||
GetInstallPath(path_cmd, drv, title_id, NULL, "content/cmd/00000001.cmd");
|
||||
|
||||
// progress update
|
||||
if (!ShowProgress(1, 5, "TMD/CMD")) return 1;
|
||||
GetInstallPath(path_tmd, drv, tid64, NULL, "content/00000000.tmd");
|
||||
GetInstallPath(path_cmd, drv, tid64, NULL, "content/cmd/00000001.cmd");
|
||||
|
||||
// copy tmd & cmd
|
||||
fvx_rmkpath(path_tmd);
|
||||
@ -1437,34 +1651,24 @@ u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
|
||||
free(cmd); // we don't need this anymore
|
||||
|
||||
// generate savedata
|
||||
if (exthdr && (exthdr->savedata_size)) {
|
||||
char path_save[128];
|
||||
|
||||
// progress update
|
||||
if (!ShowProgress(2, 5, "Savegame")) return 1;
|
||||
|
||||
// generate the path
|
||||
GetInstallSavePath(path_save, drv, title_id);
|
||||
|
||||
// generate the save file, first check if it already exists
|
||||
if (fvx_qsize(path_save) != exthdr->savedata_size) {
|
||||
static const u8 zeroes[0x20] = { 0x00 };
|
||||
UINT bw;
|
||||
FIL save;
|
||||
fvx_rmkpath(path_save);
|
||||
if (fvx_open(&save, path_save, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK)
|
||||
u32 save_size = getle32(tmd->save_size);
|
||||
u32 twl_privsave_size = getle32(tmd->twl_privsave_size);
|
||||
if (exthdr && save_size && // NCCH
|
||||
(CreateSaveData(drv, tid64, "*", save_size, false) != 0))
|
||||
return 1;
|
||||
if (!ncch && save_size && // TWL public.sav
|
||||
(CreateSaveData(drv, tid64, "public.sav", save_size, false) != 0))
|
||||
return 1;
|
||||
if (!ncch && twl_privsave_size && // TWL private.sav
|
||||
(CreateSaveData(drv, tid64, "private.sav", twl_privsave_size, false) != 0))
|
||||
return 1;
|
||||
if ((tmd->twl_flag & 0x2) && // TWL banner.sav
|
||||
(CreateSaveData(drv, tid64, "banner.sav", sizeof(TwlIconData), false) != 0))
|
||||
return 1;
|
||||
if ((fvx_write(&save, zeroes, 0x20, &bw) != FR_OK) || (bw != 0x20))
|
||||
bw = 0;
|
||||
fvx_lseek(&save, exthdr->savedata_size);
|
||||
fvx_sync(&save);
|
||||
fvx_close(&save);
|
||||
if (bw != 0x20) return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// progress update
|
||||
if (!ShowProgress(3, 5, "TitleDB update")) return 1;
|
||||
// install seed to system (if available)
|
||||
if (ncch && (SetupSystemForNcch(ncch, to_emunand) != 0))
|
||||
return 1;
|
||||
|
||||
// write ticket and title databases
|
||||
// ensure remounting the old mount path
|
||||
@ -1475,32 +1679,23 @@ u32 InstallCiaSystemData(CiaStub* cia, const char* drv) {
|
||||
|
||||
// title database
|
||||
if (!InitImgFS(path_titledb) ||
|
||||
((AddTitleInfoEntryToDB("D:/partitionA.bin", title_id, &tie, true)) != 0)) {
|
||||
((AddTitleInfoEntryToDB(PART_PATH, title_id, &tie, true)) != 0)) {
|
||||
InitImgFS(path_bak);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// progress update
|
||||
if (!ShowProgress(4, 5, "TicketDB update")) return 1;
|
||||
|
||||
// ticket database
|
||||
if (!InitImgFS(path_ticketdb) ||
|
||||
((AddTicketToDB("D:/partitionA.bin", title_id, (Ticket*) ticket, true)) != 0)) {
|
||||
((AddTicketToDB(PART_PATH, title_id, (Ticket*) ticket, true)) != 0)) {
|
||||
InitImgFS(path_bak);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// progress update
|
||||
if (!ShowProgress(5, 5, "TMD/CMD/TiE/Ticket/Save")) return 1;
|
||||
|
||||
// restore old mount path
|
||||
InitImgFS(path_bak);
|
||||
|
||||
// fix CMACs where required
|
||||
if (!syscmd) {
|
||||
cmd->unknown = 0xFFFFFFFE; // mark this as custom built
|
||||
FixFileCmac(path_cmd, true);
|
||||
}
|
||||
if (!syscmd) FixFileCmac(path_cmd, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1635,18 +1830,19 @@ u32 InstallFromCiaFile(const char* path_cia, const char* path_dest) {
|
||||
u16 index = getbe16(chunk->index);
|
||||
if (!(cnt_index[index/8] & (1 << (7-(index%8))))) continue; // don't try to install missing contents
|
||||
if (InstallCiaContent(path_dest, path_cia, next_offset, size,
|
||||
chunk, title_id, titlekey, false) != 0) {
|
||||
chunk, title_id, titlekey, false, false) != 0) {
|
||||
free(cia);
|
||||
return 1;
|
||||
}
|
||||
next_offset += size;
|
||||
}
|
||||
|
||||
// proactive fix for CIA console ID
|
||||
// fix for CIA console ID (if device ID different)
|
||||
if (getbe32(cia->ticket.console_id) != (&ARM9_ITCM->otp)->deviceId)
|
||||
memset(cia->ticket.console_id, 0x00, 4);
|
||||
|
||||
// fix TMD hashes, install CIA system data
|
||||
if ((FixTmdHashes(&(cia->tmd)) != 0) ||
|
||||
// verify TMD hashes, install CIA system data
|
||||
if ((VerifyTmd(&(cia->tmd)) != 0) ||
|
||||
(InstallCiaSystemData(cia, path_dest) != 0)) {
|
||||
free(cia);
|
||||
return 1;
|
||||
@ -1740,16 +1936,18 @@ u32 BuildInstallFromTmdFileBuffered(const char* path_tmd, const char* path_dest,
|
||||
free(ticket_tmp);
|
||||
return 1;
|
||||
}
|
||||
memcpy(ticket->titlekey, ticket_tmp->titlekey, 0x10);
|
||||
ticket->commonkey_idx = ticket_tmp->commonkey_idx;
|
||||
free(ticket_tmp);
|
||||
} else {
|
||||
Ticket* ticket_tmp;
|
||||
Ticket* ticket_tmp = NULL;
|
||||
if ((FindTitleKey(ticket, title_id) != 0) &&
|
||||
(FindTicket(&ticket_tmp, title_id, false, src_emunand) == 0)) {
|
||||
// we just copy the titlekey from a valid ticket (if we can)
|
||||
memcpy(ticket->titlekey, ticket_tmp->titlekey, 0x10);
|
||||
ticket->commonkey_idx = ticket_tmp->commonkey_idx;
|
||||
}
|
||||
free(ticket_tmp);
|
||||
if (ticket_tmp) free(ticket_tmp);
|
||||
}
|
||||
|
||||
// content path string
|
||||
@ -1797,7 +1995,7 @@ u32 BuildInstallFromTmdFileBuffered(const char* path_tmd, const char* path_dest,
|
||||
return 1;
|
||||
}
|
||||
if (install && (InstallCiaContent(path_dest, path_content, 0, (u32) getbe64(chunk->size),
|
||||
chunk, title_id, titlekey, false) != 0)) {
|
||||
chunk, title_id, titlekey, false, cdn) != 0)) {
|
||||
ShowPrompt(false, "ID %016llX.%08lX\nInstall content failed", getbe64(title_id), getbe32(chunk->id));
|
||||
return 1;
|
||||
}
|
||||
@ -1824,8 +2022,9 @@ u32 BuildInstallFromTmdFileBuffered(const char* path_tmd, const char* path_dest,
|
||||
}
|
||||
}
|
||||
|
||||
// write the CIA stub (take #2)
|
||||
if ((FixTmdHashes(tmd) != 0) ||
|
||||
// verify TMD / write CIA stub / install system data (take #2)
|
||||
if ((force_legit && (VerifyTmd(tmd) != 0)) ||
|
||||
(!force_legit && (FixTmdHashes(tmd) != 0)) ||
|
||||
(!install && (WriteCiaStub(cia, path_dest) != 0)) ||
|
||||
(install && (InstallCiaSystemData(cia, path_dest) != 0)))
|
||||
return 1;
|
||||
@ -1837,7 +2036,7 @@ u32 InstallFromTmdFile(const char* path_tmd, const char* path_dest) {
|
||||
void* buffer = (void*) malloc(sizeof(CiaStub));
|
||||
if (!buffer) return 1;
|
||||
|
||||
u32 ret = BuildInstallFromTmdFileBuffered(path_tmd, path_dest, false, true, buffer, true);
|
||||
u32 ret = BuildInstallFromTmdFileBuffered(path_tmd, path_dest, true, true, buffer, true);
|
||||
|
||||
free(buffer);
|
||||
return ret;
|
||||
@ -1847,12 +2046,23 @@ u32 BuildCiaFromTmdFile(const char* path_tmd, const char* path_dest, bool force_
|
||||
void* buffer = (void*) malloc(sizeof(CiaStub));
|
||||
if (!buffer) return 1;
|
||||
|
||||
u32 ret = BuildInstallFromTmdFileBuffered(path_tmd, path_dest, force_legit, cdn, buffer, true);
|
||||
u32 ret = BuildInstallFromTmdFileBuffered(path_tmd, path_dest, force_legit, cdn, buffer, false);
|
||||
|
||||
free(buffer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32 BuildCiaFromTieFile(const char* path_tie, const char* path_dest, bool force_legit) {
|
||||
char path_tmd[64];
|
||||
|
||||
// get the TMD path
|
||||
if (GetTieTmdPath(path_tmd, path_tie) != 0)
|
||||
return 1;
|
||||
|
||||
// let the TMD builder function take over
|
||||
return BuildCiaFromTmdFile(path_tmd, path_dest, force_legit, false);
|
||||
}
|
||||
|
||||
u32 BuildInstallFromNcchFile(const char* path_ncch, const char* path_dest, bool install) {
|
||||
NcchExtHeader exthdr;
|
||||
NcchHeader ncch;
|
||||
@ -1880,7 +2090,7 @@ u32 BuildInstallFromNcchFile(const char* path_ncch, const char* path_dest, bool
|
||||
if ((BuildCiaHeader(&(cia->header), TICKET_COMMON_SIZE) != 0) ||
|
||||
(BuildCiaCert(cia->cert) != 0) ||
|
||||
(BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) ||
|
||||
(BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, 0)) ||
|
||||
(BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, 0, 0)) ||
|
||||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
|
||||
(!install && (WriteCiaStub(cia, path_dest) != 0))) {
|
||||
free(cia);
|
||||
@ -1891,7 +2101,7 @@ u32 BuildInstallFromNcchFile(const char* path_ncch, const char* path_dest, bool
|
||||
TmdContentChunk* chunk = cia->content_list;
|
||||
memset(chunk, 0, sizeof(TmdContentChunk)); // nothing else to do
|
||||
if ((!install && (InsertCiaContent(path_dest, path_ncch, 0, 0, chunk, NULL, false, true, false) != 0)) ||
|
||||
(install && (InstallCiaContent(path_dest, path_ncch, 0, 0, chunk, title_id, NULL, true) != 0))) {
|
||||
(install && (InstallCiaContent(path_dest, path_ncch, 0, 0, chunk, title_id, NULL, true, false) != 0))) {
|
||||
free(cia);
|
||||
return 1;
|
||||
}
|
||||
@ -1950,7 +2160,7 @@ u32 BuildInstallFromNcsdFile(const char* path_ncsd, const char* path_dest, bool
|
||||
if ((BuildCiaHeader(&(cia->header), TICKET_COMMON_SIZE) != 0) ||
|
||||
(BuildCiaCert(cia->cert) != 0) ||
|
||||
(BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) ||
|
||||
(BuildFakeTmd(&(cia->tmd), title_id, content_count, save_size, 0)) ||
|
||||
(BuildFakeTmd(&(cia->tmd), title_id, content_count, save_size, 0, 0)) ||
|
||||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
|
||||
(!install && (WriteCiaStub(cia, path_dest) != 0))) {
|
||||
free(cia);
|
||||
@ -1970,7 +2180,7 @@ u32 BuildInstallFromNcsdFile(const char* path_ncsd, const char* path_dest, bool
|
||||
if ((!install && (InsertCiaContent(path_dest, path_ncsd,
|
||||
offset, size, chunk++, NULL, false, (i == 0), false) != 0)) ||
|
||||
(install && (InstallCiaContent(path_dest, path_ncsd,
|
||||
offset, size, chunk++, title_id, NULL, (i == 0)) != 0))) {
|
||||
offset, size, chunk++, title_id, NULL, (i == 0), false) != 0))) {
|
||||
free(cia);
|
||||
return 1;
|
||||
}
|
||||
@ -2015,17 +2225,19 @@ u32 BuildInstallFromNdsFile(const char* path_nds, const char* path_dest, bool in
|
||||
u8 title_id[8];
|
||||
u32 save_size = 0;
|
||||
u32 privsave_size = 0;
|
||||
u8 twl_flag = 0;
|
||||
|
||||
// Init progress bar
|
||||
if (!ShowProgress(0, 0, path_nds)) return 1;
|
||||
|
||||
// load TWL header, get save sizes && title id
|
||||
// load TWL header, get save sizes, srl flag && title id
|
||||
if (fvx_qread(path_nds, &twl, 0, sizeof(TwlHeader), NULL) != FR_OK)
|
||||
return 1;
|
||||
for (u32 i = 0; i < 8; i++)
|
||||
title_id[i] = (twl.title_id >> ((7-i)*8)) & 0xFF;
|
||||
save_size = twl.pubsav_size;
|
||||
privsave_size = twl.prvsav_size;
|
||||
twl_flag = twl.srl_flag;
|
||||
|
||||
// some basic sanity checks
|
||||
// see: https://problemkaputt.de/gbatek.htm#dsicartridgeheader
|
||||
@ -2045,7 +2257,7 @@ u32 BuildInstallFromNdsFile(const char* path_nds, const char* path_dest, bool in
|
||||
if ((BuildCiaHeader(&(cia->header), TICKET_COMMON_SIZE) != 0) ||
|
||||
(BuildCiaCert(cia->cert) != 0) ||
|
||||
(BuildFakeTicket((Ticket*)&(cia->ticket), title_id) != 0) ||
|
||||
(BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, privsave_size)) ||
|
||||
(BuildFakeTmd(&(cia->tmd), title_id, 1, save_size, privsave_size, twl_flag)) ||
|
||||
(FixCiaHeaderForTmd(&(cia->header), &(cia->tmd)) != 0) ||
|
||||
(!install && (WriteCiaStub(cia, path_dest) != 0))) {
|
||||
free(cia);
|
||||
@ -2056,7 +2268,7 @@ u32 BuildInstallFromNdsFile(const char* path_nds, const char* path_dest, bool in
|
||||
TmdContentChunk* chunk = cia->content_list;
|
||||
memset(chunk, 0, sizeof(TmdContentChunk)); // nothing else to do
|
||||
if ((!install && (InsertCiaContent(path_dest, path_nds, 0, 0, chunk, NULL, false, false, false) != 0)) ||
|
||||
(install && (InstallCiaContent(path_dest, path_nds, 0, 0, chunk, title_id, NULL, false) != 0))) {
|
||||
(install && (InstallCiaContent(path_dest, path_nds, 0, 0, chunk, title_id, NULL, false, false) != 0))) {
|
||||
free(cia);
|
||||
return 1;
|
||||
}
|
||||
@ -2083,7 +2295,7 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) {
|
||||
// build output name
|
||||
snprintf(dest, 256, OUTPUT_PATH "/");
|
||||
char* dname = dest + strnlen(dest, 256);
|
||||
if (!((filetype & GAME_TMD) || (strncmp(path + 1, ":/title/", 8) == 0)) ||
|
||||
if (!((filetype & (GAME_TMD|GAME_TIE)) || (strncmp(path + 1, ":/title/", 8) == 0)) ||
|
||||
(GetGoodName(dname, path, false) != 0)) {
|
||||
char* name = strrchr(path, '/');
|
||||
if (!name) return 1;
|
||||
@ -2103,7 +2315,9 @@ u32 BuildCiaFromGameFile(const char* path, bool force_legit) {
|
||||
return 1;
|
||||
|
||||
// build CIA from game file
|
||||
if (filetype & GAME_TMD)
|
||||
if (filetype & GAME_TIE)
|
||||
ret = BuildCiaFromTieFile(path, dest, force_legit);
|
||||
else if (filetype & GAME_TMD)
|
||||
ret = BuildCiaFromTmdFile(path, dest, force_legit, filetype & FLAG_NUSCDN);
|
||||
else if (filetype & GAME_NCCH)
|
||||
ret = BuildInstallFromNcchFile(path, dest, false);
|
||||
@ -2153,32 +2367,31 @@ u64 GetGameFileTitleId(const char* path) {
|
||||
}
|
||||
|
||||
u32 InstallGameFile(const char* path, bool to_emunand) {
|
||||
const char* drv;
|
||||
char drv[3];
|
||||
u64 filetype = IdentifyFileType(path);
|
||||
u32 ret = 0;
|
||||
|
||||
// find out the destination
|
||||
bool to_sd = false;
|
||||
bool to_twl = false;
|
||||
u64 tid64 = GetGameFileTitleId(path);
|
||||
if (!tid64) return 1;
|
||||
if (((tid64 >> 32) & 0x8000) || (filetype & GAME_NDS))
|
||||
to_twl = true;
|
||||
else if (!((tid64 >> 32) & 0x10))
|
||||
to_sd = true;
|
||||
|
||||
// does the title.db exist?
|
||||
if ((to_sd && !fvx_qsize(to_emunand ? "B:/dbs/title.db" : "A:/dbs/title.db")) ||
|
||||
(!to_sd && !fvx_qsize(to_emunand ? "4:/dbs/title.db" : "1:/dbs/title.db")))
|
||||
if (GetInstallDataDrive(drv, tid64, to_emunand) != 0)
|
||||
return 1;
|
||||
|
||||
// now we know the correct drive
|
||||
drv = to_emunand ? (to_sd ? "B:" : to_twl ? "5:" : "4:") :
|
||||
(to_sd ? "A:" : to_twl ? "2:" : "1:");
|
||||
// check dbs
|
||||
char path_db[32];
|
||||
if (((GetInstallDbsPath(path_db, drv, "title.db" ) != 0) || !fvx_qsize(path_db)) ||
|
||||
((GetInstallDbsPath(path_db, drv, "import.db") != 0) || !fvx_qsize(path_db)) ||
|
||||
((GetInstallDbsPath(path_db, drv, "ticket.db") != 0) || !fvx_qsize(path_db))) {
|
||||
ShowPrompt(false, "Install error:\nThis system is missing one or\nmore .db files.\n \nMaybe the SD card is missing\nor uninitialized?");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// check permissions for SysNAND (this includes everything we need)
|
||||
if (!CheckWritePermissions(to_emunand ? "4:" : "1:")) return 1;
|
||||
|
||||
// cleanup content folder before starting install
|
||||
ShowProgress(0, 0, path);
|
||||
UninstallGameData(tid64, false, false, false, to_emunand);
|
||||
|
||||
// install game file
|
||||
if (filetype & GAME_CIA)
|
||||
ret = InstallFromCiaFile(path, drv);
|
||||
@ -2192,9 +2405,8 @@ u32 InstallGameFile(const char* path, bool to_emunand) {
|
||||
ret = BuildInstallFromNdsFile(path, drv, true);
|
||||
else ret = 1;
|
||||
|
||||
// we have no clue what to do on failure
|
||||
// if (ret != 0) ...
|
||||
// maybe just uninstall?
|
||||
// cleanup on failed installs, but leave ticket and save untouched
|
||||
if (ret != 0) UninstallGameData(tid64, true, false, false, to_emunand);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -2425,6 +2637,10 @@ u32 LoadSmdhFromGameFile(const char* path, Smdh* smdh) {
|
||||
char path_content[256];
|
||||
if (GetTmdContentPath(path_content, path) != 0) return 1;
|
||||
return LoadSmdhFromGameFile(path_content, smdh);
|
||||
} else if (filetype & GAME_TIE) {
|
||||
char path_content[256];
|
||||
if (GetTieContentPath(path_content, path) != 0) return 1;
|
||||
return LoadSmdhFromGameFile(path_content, smdh);
|
||||
} else if (filetype & GAME_3DSX) {
|
||||
ThreedsxHeader threedsx;
|
||||
if ((fvx_qread(path, &threedsx, 0, sizeof(ThreedsxHeader), NULL) != FR_OK) ||
|
||||
@ -2483,6 +2699,9 @@ u32 ShowGameFileTitleInfoF(const char* path, u16* screen, bool clear) {
|
||||
if (itype & GAME_TMD) {
|
||||
if (GetTmdContentPath(path_content, path) != 0) return 1;
|
||||
path = path_content;
|
||||
} else if (itype & GAME_TIE) {
|
||||
if (GetTieContentPath(path_content, path) != 0) return 1;
|
||||
path = path_content;
|
||||
}
|
||||
|
||||
void* buffer = (void*) malloc(max(sizeof(Smdh), sizeof(TwlIconData)));
|
||||
@ -2789,45 +3008,27 @@ u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) {
|
||||
return 1;
|
||||
}
|
||||
} else if (filetype & SYS_TICKDB) {
|
||||
if (!InitImgFS(path_in))
|
||||
return 1;
|
||||
u32 num_entries = GetNumTickets(PART_PATH);
|
||||
if (!num_entries) return 1;
|
||||
u8* title_ids = (u8*) malloc(num_entries * 8);
|
||||
if (!title_ids) return 1;
|
||||
|
||||
DIR dir;
|
||||
FILINFO fno;
|
||||
TicketCommon ticket;
|
||||
char tik_path[64];
|
||||
|
||||
if (fvx_opendir(&dir, "T:/eshop") != FR_OK) {
|
||||
if (!InitImgFS(path_in) || (ListTicketTitleIDs(PART_PATH, title_ids, num_entries) != 0)) {
|
||||
free(title_ids);
|
||||
InitImgFS(NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
while ((fvx_readdir(&dir, &fno) == FR_OK) && *(fno.fname)) {
|
||||
snprintf(tik_path, 64, "T:/eshop/%s", fno.fname);
|
||||
if (fvx_qread(tik_path, &ticket, 0, TICKET_COMMON_SIZE, NULL) != FR_OK)
|
||||
continue;
|
||||
if (TIKDB_SIZE(tik_info) + 32 > STD_BUFFER_SIZE) break; // no error message
|
||||
AddTicketToInfo(tik_info, (Ticket*) &ticket, dec); // ignore result
|
||||
// read and validate all tickets, add validated to info
|
||||
for (u32 i = 0; i < num_entries; i++) {
|
||||
Ticket* ticket;
|
||||
if (ReadTicketFromDB(PART_PATH, title_ids + (i * 8), &ticket) != 0) continue;
|
||||
if (ValidateTicketSignature(ticket) == 0)
|
||||
AddTicketToInfo(tik_info, ticket, dec); // ignore result
|
||||
free(ticket);
|
||||
}
|
||||
|
||||
fvx_closedir(&dir);
|
||||
|
||||
if (fvx_opendir(&dir, "T:/system") != FR_OK) {
|
||||
InitImgFS(NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
while ((fvx_readdir(&dir, &fno) == FR_OK) && *(fno.fname)) {
|
||||
snprintf(tik_path, 64, "T:/system/%s", fno.fname);
|
||||
if ((fvx_qread(tik_path, &ticket, 0, TICKET_COMMON_SIZE, NULL) != FR_OK) ||
|
||||
(ValidateTicketSignature((Ticket*) &ticket) != 0))
|
||||
continue;
|
||||
if (TIKDB_SIZE(tik_info) + 32 > STD_BUFFER_SIZE) break; // no error message
|
||||
AddTicketToInfo(tik_info, (Ticket*) &ticket, dec); // ignore result
|
||||
}
|
||||
|
||||
fvx_closedir(&dir);
|
||||
|
||||
free(title_ids);
|
||||
InitImgFS(NULL);
|
||||
} else if (filetype & BIN_TIKDB) {
|
||||
TitleKeysInfo* tik_info_merge = (TitleKeysInfo*) malloc(STD_BUFFER_SIZE);
|
||||
@ -2871,7 +3072,7 @@ u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) {
|
||||
|
||||
u32 BuildSeedInfo(const char* path, bool dump) {
|
||||
static SeedInfo* seed_info = NULL;
|
||||
const char* path_out = OUTPUT_PATH "/" SEEDDB_NAME;
|
||||
const char* path_out = OUTPUT_PATH "/" SEEDINFO_NAME;
|
||||
const char* path_in = path;
|
||||
u32 inputtype = 0; // 0 -> none, 1 -> seeddb.bin, 2 -> seed system save
|
||||
|
||||
@ -2896,16 +3097,7 @@ u32 BuildSeedInfo(const char* path, bool dump) {
|
||||
|
||||
char path_str[128];
|
||||
if (path_in && (strnlen(path_in, 16) == 2)) { // when only a drive is given...
|
||||
// grab the key Y from movable.sed
|
||||
u8 movable_keyy[16] __attribute__((aligned(4)));
|
||||
snprintf(path_str, 128, "%s/private/movable.sed", path_in);
|
||||
if (fvx_qread(path_str, movable_keyy, 0x110, 0x10, NULL) != FR_OK)
|
||||
return 1;
|
||||
// build the seed save path
|
||||
u32 sha256sum[8];
|
||||
sha_quick(sha256sum, movable_keyy, 0x10, SHA256_MODE);
|
||||
snprintf(path_str, 128, "%s/data/%08lX%08lX%08lX%08lX/sysdata/0001000F/00000000",
|
||||
path_in, sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]);
|
||||
if (GetSeedPath(path_str, path_in) != 0) return 1;
|
||||
path_in = path_str;
|
||||
inputtype = 2;
|
||||
}
|
||||
@ -2916,7 +3108,7 @@ u32 BuildSeedInfo(const char* path, bool dump) {
|
||||
|
||||
UINT br;
|
||||
if ((fvx_qread(path_in, seed_info_merge, 0, STD_BUFFER_SIZE, &br) != FR_OK) ||
|
||||
(SEEDDB_SIZE(seed_info_merge) != br)) {
|
||||
(SEEDINFO_SIZE(seed_info_merge) != br)) {
|
||||
free(seed_info_merge);
|
||||
return 1;
|
||||
}
|
||||
@ -2925,27 +3117,27 @@ u32 BuildSeedInfo(const char* path, bool dump) {
|
||||
u32 n_entries = seed_info_merge->n_entries;
|
||||
SeedInfoEntry* seed = seed_info_merge->entries;
|
||||
for (u32 i = 0; i < n_entries; i++, seed++) {
|
||||
if (SEEDDB_SIZE(seed_info) + 32 > STD_BUFFER_SIZE) break; // no error message
|
||||
if (SEEDINFO_SIZE(seed_info) + 32 > STD_BUFFER_SIZE) break; // no error message
|
||||
AddSeedToDb(seed_info, seed); // ignore result
|
||||
}
|
||||
|
||||
free(seed_info_merge);
|
||||
} else if (inputtype == 2) { // seed system save input
|
||||
u8* seedsave = (u8*) malloc(SEEDSAVE_AREA_SIZE);
|
||||
SeedDb* seedsave = (SeedDb*) malloc(sizeof(SeedDb));
|
||||
if (!seedsave) return 1;
|
||||
|
||||
if (ReadDisaDiffIvfcLvl4(path_in, NULL, SEEDSAVE_AREA_OFFSET, SEEDSAVE_AREA_SIZE, seedsave) != SEEDSAVE_AREA_SIZE) {
|
||||
if ((ReadDisaDiffIvfcLvl4(path_in, NULL, SEEDSAVE_AREA_OFFSET, sizeof(SeedDb), seedsave) != sizeof(SeedDb)) ||
|
||||
(seedsave->n_entries >= SEEDSAVE_MAX_ENTRIES)) {
|
||||
free(seedsave);
|
||||
return 1;
|
||||
}
|
||||
|
||||
SeedInfoEntry seed = { 0 };
|
||||
for (u32 s = 0; s < SEEDSAVE_MAX_ENTRIES; s++) {
|
||||
seed.titleId = getle64(seedsave + (s*8));
|
||||
memcpy(seed.seed, seedsave + (SEEDSAVE_MAX_ENTRIES*8) + (s*16), 16);
|
||||
if (((seed.titleId >> 32) != 0x00040000) ||
|
||||
(!getle64(seed.seed) && !getle64(seed.seed + 8))) continue;
|
||||
if (SEEDDB_SIZE(seed_info) + 32 > STD_BUFFER_SIZE) break; // no error message
|
||||
for (u32 s = 0; s < seedsave->n_entries; s++) {
|
||||
seed.titleId = seedsave->titleId[s];
|
||||
memcpy(&(seed.seed), &(seedsave->seed[s]), sizeof(Seed));
|
||||
if ((seed.titleId >> 32) != 0x00040000) continue;
|
||||
if (SEEDINFO_SIZE(seed_info) + 32 > STD_BUFFER_SIZE) break; // no error message
|
||||
AddSeedToDb(seed_info, &seed); // ignore result
|
||||
}
|
||||
|
||||
@ -2953,7 +3145,7 @@ u32 BuildSeedInfo(const char* path, bool dump) {
|
||||
}
|
||||
|
||||
if (dump) {
|
||||
u32 dump_size = SEEDDB_SIZE(seed_info);
|
||||
u32 dump_size = SEEDINFO_SIZE(seed_info);
|
||||
u32 ret = 0;
|
||||
|
||||
if (dump_size > 16) {
|
||||
@ -3039,7 +3231,8 @@ u32 GetGoodName(char* name, const char* path, bool quick) {
|
||||
(type_donor & GAME_NCCH) ? ((type_donor & FLAG_CXI) ? "cxi" : "cfa") :
|
||||
(type_donor & GAME_NDS) ? "nds" :
|
||||
(type_donor & GAME_GBA) ? "gba" :
|
||||
(type_donor & GAME_TMD) ? "tmd" : "";
|
||||
(type_donor & GAME_TMD) ? "tmd" :
|
||||
(type_donor & GAME_TIE) ? "tie" : "";
|
||||
if (!*ext) return 1;
|
||||
|
||||
char appid_str[1 + 8 + 1] = { 0 }; // handling for NCCH / NDS in "?:/title" paths
|
||||
@ -3055,6 +3248,10 @@ u32 GetGoodName(char* name, const char* path, bool quick) {
|
||||
if (GetTmdContentPath(path_content, path) != 0) return 1;
|
||||
path_donor = path_content;
|
||||
type_donor = IdentifyFileType(path_donor);
|
||||
} else if (type_donor & GAME_TIE) {
|
||||
if (GetTieContentPath(path_content, path) != 0) return 1;
|
||||
path_donor = path_content;
|
||||
type_donor = IdentifyFileType(path_donor);
|
||||
}
|
||||
|
||||
if (type_donor & GAME_GBA) { // AGB
|
||||
|
@ -15,7 +15,9 @@ u32 TrimGameFile(const char* path);
|
||||
u32 ShowGameFileTitleInfoF(const char* path, u16* screen, bool clear);
|
||||
u32 ShowGameFileTitleInfo(const char* path);
|
||||
u32 ShowCiaCheckerInfo(const char* path);
|
||||
u32 UninstallGameDataTie(const char* path, bool remove_tie, bool remove_ticket, bool remove_save);
|
||||
u32 GetTmdContentPath(char* path_content, const char* path_tmd);
|
||||
u32 GetTieContentPath(char* path_content, const char* path_tie);
|
||||
u32 BuildNcchInfoXorpads(const char* destdir, const char* path);
|
||||
u32 CheckHealthAndSafetyInject(const char* hsdrv);
|
||||
u32 InjectHealthAndSafety(const char* path, const char* destdrv);
|
||||
|
@ -1363,8 +1363,8 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
(BuildTitleKeyInfo(NULL, tik_dec, true) == 0))
|
||||
ret = true;
|
||||
}
|
||||
} else if (strncasecmp(argv[0], SEEDDB_NAME, _ARG_MAX_LEN) == 0) {
|
||||
if (flags & _FLG('w')) fvx_unlink(OUTPUT_PATH "/" SEEDDB_NAME);
|
||||
} else if (strncasecmp(argv[0], SEEDINFO_NAME, _ARG_MAX_LEN) == 0) {
|
||||
if (flags & _FLG('w')) fvx_unlink(OUTPUT_PATH "/" SEEDINFO_NAME);
|
||||
if (BuildSeedInfo(NULL, false) == 0) {
|
||||
ShowString("Building to " OUTPUT_PATH ":\n%s ...", argv[0]);
|
||||
if (((BuildSeedInfo("1:", false) == 0) ||
|
||||
@ -1427,6 +1427,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
DeinitExtFS();
|
||||
DeinitSDCardFS();
|
||||
PXI_DoCMD(PXICMD_LEGACY_BOOT, NULL, 0);
|
||||
PXI_Barrier(PXI_FIRMLAUNCH_BARRIER);
|
||||
BootFirm((FirmHeader*)(void*)firm, fixpath);
|
||||
while(1);
|
||||
} else if (err_str) snprintf(err_str, _ERR_STR_LEN, "not a bootable firm");
|
||||
|
@ -135,7 +135,7 @@ bool ReadVBDRIDir(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
continue;
|
||||
|
||||
memset(vfile, 0, sizeof(VirtualFile));
|
||||
snprintf(vfile->name, 32, NAME_TIK, tid, getbe32(tick_info[vdir->index].console_id));
|
||||
snprintf(vfile->name, 32, NAME_TIK, tid, getle32(tick_info[vdir->index].console_id));
|
||||
vfile->offset = vdir->index; // "offset" is the internal buffer index
|
||||
vfile->size = tick_info[vdir->index].size;
|
||||
vfile->keyslot = 0xFF;
|
||||
|
@ -47,7 +47,8 @@ enum {
|
||||
* those used by any other software
|
||||
*/
|
||||
enum {
|
||||
ARM11_READY_BARRIER = 19,
|
||||
PXI_BOOT_BARRIER = 21,
|
||||
PXI_FIRMLAUNCH_BARRIER = 154,
|
||||
};
|
||||
|
||||
#define PXI_FIFO_LEN (16)
|
||||
|
@ -24,9 +24,9 @@ typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef int64_t s8;
|
||||
typedef int64_t s16;
|
||||
typedef int64_t s32;
|
||||
typedef int8_t s8;
|
||||
typedef int16_t s16;
|
||||
typedef int32_t s32;
|
||||
typedef int64_t s64;
|
||||
|
||||
typedef volatile u8 vu8;
|
||||
|
@ -9,7 +9,7 @@ end
|
||||
@Start
|
||||
set PREVIEW_MODE "GODMODE9 ALL-IN-ONE MEGASCRIPT\nby annson24\n \nCredits:\nd0k3\n8bitwonder\nwindows_server_2003\nSvenDaHacker64\nMyLegGuy\nemillois\nAnalogMan151\nTurdPooCharger\netc."
|
||||
|
||||
labelsel -o -s "Choose an Option." MainMenu_*
|
||||
labelsel -s "Choose an Option." MainMenu_*
|
||||
goto Start
|
||||
|
||||
##################Backup Options##################
|
||||
@ -1216,6 +1216,7 @@ rm -o -s 0:/boot9strap
|
||||
for 0:/cias *
|
||||
rm -o -s $[FORPATH]
|
||||
next
|
||||
|
||||
rm -o -s 0:/gm9/scripts/setup_ctrnand_luma3ds.gm9
|
||||
rm -o -s 0:/gm9/scripts/cleanup_sd_card.gm9
|
||||
|
||||
@ -1232,9 +1233,13 @@ rm -o -s 0:/frogcert.bin
|
||||
rm -o -s 0:/private/ds/app/4B47554A/001/T00031_1038C2A757B77_000.ppm
|
||||
rm -o -s 0:/3ds/Frogtool.3dsx
|
||||
rm -o -s 0:/3ds/squirrelboot.3dsx
|
||||
rm -o -s 0:/3ds/slotTool.3dsx
|
||||
rm -o -s 0:/usm.bin
|
||||
rm -o -s "A:/Nintendo DSiWare/F00D43D5.bin"
|
||||
|
||||
rm -o -s 0:/browserhax_hblauncher_ropbin_payload.bin
|
||||
rm -o -s 0:/arm11code.bin
|
||||
|
||||
echo "SD card now squeaky clean from setup files."
|
||||
goto MainMenu_Scripts_from_Plailect's_Guide
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user