mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-25 21:22:47 +00:00
Remove trailing white space
- Removed trailing whitespace from all source code files (.c, .h. and .s) and the README.md
This commit is contained in:
parent
d682a65df6
commit
d010f2858b
@ -113,7 +113,7 @@ With the possibilites GodMode9 provides, not everything may be obvious at first
|
||||
* __Hexview and hexedit any file__: Press the A button on a file and select `Show in Hexeditor`. A button again enables edit mode, hold the A button and press arrow buttons to edit bytes. You will get an additional confirmation prompt to take over changes. Take note that for certain files, write permissions can't be enabled.
|
||||
* __View text files in a text viewer__: Press the A button on a file and select `Show in Textviewer` (only shows up for actual text files). You can enable wordwrapped mode via R+Y, and navigate around the file via R+X and the dpad.
|
||||
* __Chainload FIRM payloads__: Press the A button on a FIRM file, select `FIRM options` -> `Boot FIRM`. Keep in mind you should not run FIRMs from dubious sources and that the write permissions system is no longer in place after booting a payload.
|
||||
* __Chainload FIRM payloads from a neat menu__: The `payloads` menu is found inside the HOME button menu. It provides any FIRM found in `0:/gm9/payloads` for quick chainloading.
|
||||
* __Chainload FIRM payloads from a neat menu__: The `payloads` menu is found inside the HOME button menu. It provides any FIRM found in `0:/gm9/payloads` for quick chainloading.
|
||||
* __Inject a file to another file__: Put exactly one file (the file to be injected from) into the clipboard (via the Y button). Press A on the file to be injected to. There will be an option to inject the first file into it.
|
||||
|
||||
### Scripting functionality
|
||||
|
@ -31,7 +31,7 @@ enum {
|
||||
GIC_RISINGEDGE_1N = 3
|
||||
|
||||
// With the 1-N model, an interrupt that is taken on any CPU clears the Pending
|
||||
// status on all CPUs.
|
||||
// status on all CPUs.
|
||||
// With the N-N model, all CPUs receive the interrupt independently. The Pending
|
||||
// status is cleared only for the CPU that takes it, not for the other CPUs
|
||||
};
|
||||
|
@ -7,23 +7,23 @@ bool is_valid_dstime(DsTime* dstime) {
|
||||
(DSTIMEGET(dstime, bcd_m) >= 60) ||
|
||||
(DSTIMEGET(dstime, bcd_s) >= 60))
|
||||
return false;
|
||||
|
||||
|
||||
// check the date...
|
||||
u32 year = 2000 + DSTIMEGET(dstime, bcd_Y);
|
||||
u32 month = DSTIMEGET(dstime, bcd_M);
|
||||
u32 day = DSTIMEGET(dstime, bcd_D);
|
||||
|
||||
|
||||
// date: year & month
|
||||
if ((year >= 2100) || (month == 0) || (month > 12))
|
||||
return false;
|
||||
|
||||
|
||||
// date: day
|
||||
// see: https://github.com/devkitPro/libnds/blob/9678bf09389cb1fcdc99dfa0357ec0cbe51dd0b7/source/arm7/clock.c#L224-L262
|
||||
u32 months_lastday[1+12] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||
u32 leap = (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) ? 1 : 0;
|
||||
u32 days_in_month = months_lastday[month] + ((month == 2) ? leap : 0);
|
||||
if (day > days_in_month) return false;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ static bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout) {
|
||||
|
||||
static void DrawKey(const TouchBox* key, const bool pressed, const u32 uppercase) {
|
||||
const char* keystrs[] = { SWKBD_KEYSTR };
|
||||
const u32 color = (pressed) ? COLOR_SWKBD_PRESSED :
|
||||
const u32 color = (pressed) ? COLOR_SWKBD_PRESSED :
|
||||
(key->id == KEY_ENTER) ? COLOR_SWKBD_ENTER :
|
||||
((key->id == KEY_CAPS) && (uppercase > 1)) ? COLOR_SWKBD_CAPS :
|
||||
COLOR_SWKBD_NORMAL;
|
||||
@ -125,12 +125,12 @@ static void DrawTextBox(const TouchBox* txtbox, const char* inputstr, const u32
|
||||
const u32 input_shown = (txtbox->w / FONT_WIDTH_EXT) - 2;
|
||||
const u32 inputstr_size = strlen(inputstr); // we rely on a zero terminated string
|
||||
const u16 x = txtbox->x;
|
||||
const u16 y = txtbox->y;
|
||||
|
||||
const u16 y = txtbox->y;
|
||||
|
||||
// fix scroll
|
||||
if (cursor < *scroll) *scroll = cursor;
|
||||
else if (cursor - *scroll > input_shown) *scroll = cursor - input_shown;
|
||||
|
||||
|
||||
// draw input string
|
||||
DrawStringF(BOT_SCREEN, x, y, COLOR_STD_FONT, COLOR_STD_BG, "%c%-*.*s%-*.*s%c",
|
||||
(*scroll) ? '<' : '|',
|
||||
@ -142,7 +142,7 @@ static void DrawTextBox(const TouchBox* txtbox, const char* inputstr, const u32
|
||||
"",
|
||||
(inputstr_size - (s32) *scroll > input_shown) ? '>' : '|'
|
||||
);
|
||||
|
||||
|
||||
// draw cursor
|
||||
DrawStringF(BOT_SCREEN, x-(FONT_WIDTH_EXT/2), y+10, COLOR_STD_FONT, COLOR_STD_BG, "%-*.*s^%-*.*s",
|
||||
1 + cursor - *scroll,
|
||||
|
@ -7,7 +7,7 @@ u64 timer_start( void ) {
|
||||
if (!(*TIMER_CNT0 & *TIMER_CNT1 & *TIMER_CNT2 & *TIMER_CNT3 & TIMER_ACTIVE) ||
|
||||
!(*TIMER_CNT1 & *TIMER_CNT2 & *TIMER_CNT3 & TIMER_COUNT_UP))
|
||||
timer_init = true;
|
||||
|
||||
|
||||
if (timer_init) {
|
||||
// deactivate, then reset timers
|
||||
*TIMER_CNT0 = 0;
|
||||
@ -17,7 +17,7 @@ u64 timer_start( void ) {
|
||||
// start timers
|
||||
*TIMER_CNT0 = TIMER_ACTIVE;
|
||||
*TIMER_CNT1 = *TIMER_CNT2 = *TIMER_CNT3 = TIMER_ACTIVE | TIMER_COUNT_UP;
|
||||
|
||||
|
||||
// timer initialized
|
||||
timer_init = false;
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ bool CalibrateTouchFromSupportFile(void) {
|
||||
size_t size = LoadSupportFile(TOUCH_CALIB_FILENAME, calibrations, sizeof(calibrations));
|
||||
u32 n_dots = size / sizeof(HID_CalibrationData);
|
||||
|
||||
is_calibrated = (n_dots == 0) ? false :
|
||||
is_calibrated = (n_dots == 0) ? false :
|
||||
HID_SetCalibrationData(calibrations, n_dots, SCREEN_WIDTH_BOT, SCREEN_HEIGHT);
|
||||
return is_calibrated;
|
||||
}
|
||||
@ -99,11 +99,11 @@ bool CalibrateTouchFromFlash(void) {
|
||||
|
||||
// set calibration defaults
|
||||
SetCalibrationDefaults();
|
||||
|
||||
|
||||
// check SPIflash status
|
||||
if (!spiflash_get_status())
|
||||
return false;
|
||||
|
||||
|
||||
// check NVRAM console ID
|
||||
u32 console_id = 0;
|
||||
if (!spiflash_read(0x001C, 4, (u8*)&console_id))
|
||||
|
@ -19,7 +19,7 @@
|
||||
#define STRBUF_SIZE 512 // maximum size of the string buffer
|
||||
#define FONT_MAX_WIDTH 8
|
||||
#define FONT_MAX_HEIGHT 10
|
||||
#define PROGRESS_REFRESH_RATE 30 // the progress bar is only allowed to draw to screen every X milliseconds
|
||||
#define PROGRESS_REFRESH_RATE 30 // the progress bar is only allowed to draw to screen every X milliseconds
|
||||
|
||||
static u32 font_width = 0;
|
||||
static u32 font_height = 0;
|
||||
@ -247,7 +247,7 @@ void DrawString(u16 *screen, const char *str, int x, int y, u32 color, u32 bgcol
|
||||
size_t len = (strlen(str) > max_len) ? max_len : strlen(str);
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char c = (char) (fix_utf8 && str[i] >= 0x80) ? '?' : str[i];
|
||||
char c = (char) (fix_utf8 && str[i] >= 0x80) ? '?' : str[i];
|
||||
DrawCharacter(screen, c, x + i * font_width, y, color, bgcolor);
|
||||
}
|
||||
}
|
||||
@ -445,17 +445,17 @@ void ShowIconString(u16* icon, int w, int h, const char *format, ...)
|
||||
bool ShowPrompt(bool ask, const char *format, ...)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
|
||||
char str[STRBUF_SIZE];
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
vsnprintf(str, STRBUF_SIZE, format, va);
|
||||
va_end(va);
|
||||
|
||||
|
||||
ClearScreenF(true, false, COLOR_STD_BG);
|
||||
DrawStringCenter(MAIN_SCREEN, COLOR_STD_FONT, COLOR_STD_BG, "%s\n \n%s", str,
|
||||
(ask) ? "(<A> yes, <B> no)" : "(<A> to continue)");
|
||||
|
||||
|
||||
while (true) {
|
||||
u32 pad_state = InputWait(0);
|
||||
if (pad_state & BUTTON_A) break;
|
||||
@ -464,9 +464,9 @@ bool ShowPrompt(bool ask, const char *format, ...)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ClearScreenF(true, false, COLOR_STD_BG);
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -661,7 +661,7 @@ u32 ShowFileScrollPrompt(u32 n, const DirEntry** options, bool hide_ext, const c
|
||||
DrawStringF(MAIN_SCREEN, x, yopt + ((line_height+2)*(i-scroll)),
|
||||
(sel == (int)i) ? COLOR_STD_FONT : COLOR_ENTRY(options[i]), COLOR_STD_BG, "%2.2s %s",
|
||||
(sel == (int)i) ? "->" : "", content_str);
|
||||
|
||||
|
||||
DrawStringF(MAIN_SCREEN, x + item_width - font_width * 11, yopt + ((line_height+2)*(i-scroll)),
|
||||
(sel == (int)i) ? COLOR_STD_FONT : COLOR_ENTRY(options[i]), COLOR_STD_BG, "%10.10s",
|
||||
(options[i]->type == T_DIR) ? "(dir)" : (options[i]->type == T_DOTDOT) ? "(..)" : bytestr);
|
||||
@ -768,7 +768,7 @@ bool ShowInputPrompt(char* inputstr, u32 max_size, u32 resize, const char* alpha
|
||||
if (str_width < (24 * font_width)) str_width = 24 * font_width;
|
||||
x = (str_width >= SCREEN_WIDTH_MAIN) ? 0 : (SCREEN_WIDTH_MAIN - str_width) / 2;
|
||||
y = (str_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - str_height) / 2;
|
||||
|
||||
|
||||
ClearScreenF(true, false, COLOR_STD_BG);
|
||||
DrawStringF(MAIN_SCREEN, x, y, COLOR_STD_FONT, COLOR_STD_BG, "%s", str);
|
||||
DrawStringF(MAIN_SCREEN, x + 8, y + str_height - 40, COLOR_STD_FONT, COLOR_STD_BG,
|
||||
@ -901,7 +901,7 @@ bool ShowStringPrompt(char* inputstr, u32 max_size, const char *format, ...) {
|
||||
ret = ShowInputPrompt(inputstr, max_size, 1, alphabet, format, va);
|
||||
va_end(va);
|
||||
|
||||
return ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
u64 ShowHexPrompt(u64 start_val, u32 n_digits, const char *format, ...) {
|
||||
@ -919,7 +919,7 @@ u64 ShowHexPrompt(u64 start_val, u32 n_digits, const char *format, ...) {
|
||||
} else ret = (u64) -1;
|
||||
va_end(va);
|
||||
|
||||
return ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
u64 ShowNumberPrompt(u64 start_val, const char *format, ...) {
|
||||
@ -936,7 +936,7 @@ u64 ShowNumberPrompt(u64 start_val, const char *format, ...) {
|
||||
} else ret = (u64) -1;
|
||||
va_end(va);
|
||||
|
||||
return ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ShowDataPrompt(u8* data, u32* size, const char *format, ...) {
|
||||
@ -965,7 +965,7 @@ bool ShowDataPrompt(u8* data, u32* size, const char *format, ...) {
|
||||
}
|
||||
va_end(va);
|
||||
|
||||
return ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
@ -189,7 +189,7 @@ int utf16_to_utf8(u8 *out, const u16 *in, int len_out, int len_in)
|
||||
units = decode_utf16(&code, in);
|
||||
if(units == -1)
|
||||
return -1;
|
||||
|
||||
|
||||
if (len_in >= units)
|
||||
len_in -= units;
|
||||
else return -1;
|
||||
@ -238,7 +238,7 @@ int utf8_to_utf16(u16 *out, const u8 *in, int len_out, int len_in)
|
||||
units = decode_utf8(&code, in);
|
||||
if(units == -1)
|
||||
return -1;
|
||||
|
||||
|
||||
if (len_in >= units)
|
||||
len_in -= units;
|
||||
else return -1;
|
||||
|
@ -213,11 +213,11 @@ void ctr_decrypt_byte(void *inbuf, void *outbuf, size_t size, size_t off, uint32
|
||||
uint8_t *in = inbuf;
|
||||
uint8_t *out = outbuf;
|
||||
uint32_t i;
|
||||
|
||||
|
||||
for (i=0; i<AES_BLOCK_SIZE; i++) // setup local ctr
|
||||
ctr_local[i] = ctr[i];
|
||||
add_ctr(ctr_local, off / AES_BLOCK_SIZE);
|
||||
|
||||
|
||||
if (off_fix) // handle misaligned offset (at beginning)
|
||||
{
|
||||
size_t last_byte = ((off_fix + bytes_left) >= AES_BLOCK_SIZE) ?
|
||||
@ -229,7 +229,7 @@ void ctr_decrypt_byte(void *inbuf, void *outbuf, size_t size, size_t off, uint32
|
||||
*(out++) = temp[i];
|
||||
bytes_left -= last_byte - off_fix;
|
||||
}
|
||||
|
||||
|
||||
if (bytes_left >= AES_BLOCK_SIZE)
|
||||
{
|
||||
size_t blocks = bytes_left / AES_BLOCK_SIZE;
|
||||
@ -238,7 +238,7 @@ void ctr_decrypt_byte(void *inbuf, void *outbuf, size_t size, size_t off, uint32
|
||||
out += AES_BLOCK_SIZE * blocks;
|
||||
bytes_left -= AES_BLOCK_SIZE * blocks;
|
||||
}
|
||||
|
||||
|
||||
if (bytes_left) // handle misaligned offset (at end)
|
||||
{
|
||||
for (i=0; i<bytes_left; i++)
|
||||
@ -300,7 +300,7 @@ void aes_cmac(void* inbuf, void* outbuf, size_t size)
|
||||
AES_CNT_INPUT_ENDIAN | AES_CNT_OUTPUT_ENDIAN;
|
||||
uint32_t* out = (uint32_t*) outbuf;
|
||||
uint32_t* in = (uint32_t*) inbuf;
|
||||
|
||||
|
||||
// create xorpad for last block
|
||||
set_ctr(zeroes);
|
||||
aes_decrypt(xorpad, xorpad, 1, mode);
|
||||
@ -312,7 +312,7 @@ void aes_cmac(void* inbuf, void* outbuf, size_t size)
|
||||
}
|
||||
xorpadb[15] <<= 1;
|
||||
xorpadb[15] ^= finalxor;
|
||||
|
||||
|
||||
// process blocks
|
||||
for (uint32_t i = 0; i < 4; i++)
|
||||
out[i] = 0;
|
||||
|
@ -10,7 +10,7 @@ u16 crc16_quick(const void* src, u32 len) {
|
||||
static const u16 tabval[] = { CRC16_TABVAL };
|
||||
u16* data = (u16*) src;
|
||||
u16 crc = 0xFFFF;
|
||||
|
||||
|
||||
for (len >>= 1; len; len--) {
|
||||
u16 curr = *(data++);
|
||||
for (u32 i = 0; i < 4; i++) {
|
||||
@ -21,6 +21,6 @@ u16 crc16_quick(const void* src, u32 len) {
|
||||
crc ^= tval;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ u32 crc32_calculate_from_file(const char* fileName, u32 offset, u32 length) {
|
||||
return crc32;
|
||||
}
|
||||
fvx_lseek(&inputFile, offset);
|
||||
|
||||
|
||||
bool ret = true;
|
||||
for (u64 pos = 0; (pos < length) && ret; pos += bufsiz) {
|
||||
UINT read_bytes = min(bufsiz, length - pos);
|
||||
@ -83,7 +83,7 @@ u32 crc32_calculate_from_file(const char* fileName, u32 offset, u32 length) {
|
||||
ret = false;
|
||||
if (ret) crc32 = crc32_calculate(crc32, buffer, read_bytes);
|
||||
}
|
||||
|
||||
|
||||
fvx_close(&inputFile);
|
||||
free(buffer);
|
||||
return ~crc32;
|
||||
|
@ -17,7 +17,7 @@ static u64 keyYState = 0;
|
||||
u32 GetUnitKeysType(void)
|
||||
{
|
||||
static u32 keys_type = KEYS_UNKNOWN;
|
||||
|
||||
|
||||
if (keys_type == KEYS_UNKNOWN) {
|
||||
static const u8 slot0x2CSampleRetail[16] = {
|
||||
0xBC, 0xC4, 0x16, 0x2C, 0x2A, 0x06, 0x91, 0xEE, 0x47, 0x18, 0x86, 0xB8, 0xEB, 0x2F, 0xB5, 0x48 };
|
||||
@ -35,7 +35,7 @@ u32 GetUnitKeysType(void)
|
||||
keys_type = KEYS_DEVKIT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return keys_type;
|
||||
}
|
||||
|
||||
@ -67,15 +67,15 @@ u32 CheckKeySlot(u32 keyslot, char type)
|
||||
{ 0xBC, 0x83, 0x7C, 0xC9, 0x99, 0xC8, 0x80, 0x9E, 0x8A, 0xDE, 0x4A, 0xFA, 0xAA, 0x72, 0x08, 0x28 } }
|
||||
};
|
||||
u64* state = (type == 'X') ? &keyXState : (type == 'Y') ? &keyYState : (type == 'N') ? &keyState : NULL;
|
||||
|
||||
|
||||
// just to be safe...
|
||||
if (keyslot >= 0x40)
|
||||
return 1;
|
||||
|
||||
|
||||
// check if key is already loaded
|
||||
if ((type != 'I') && ((*state >> keyslot) & 1))
|
||||
return 0;
|
||||
|
||||
|
||||
// if is not, we may still be able to verify the currently set one (for NCCH keys)
|
||||
for (u32 p = 0; (type == 'X') && (p < sizeof(keyNcchSamples) / sizeof(AesNcchSampleInfo)); p++) {
|
||||
if (keyNcchSamples[p].slot != keyslot) // only for keyslots in the keyNcchSamples table!
|
||||
@ -93,7 +93,7 @@ u32 CheckKeySlot(u32 keyslot, char type)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// not set up if getting here
|
||||
return 1;
|
||||
}
|
||||
@ -102,13 +102,13 @@ u32 CheckKeySlot(u32 keyslot, char type)
|
||||
// to get rid of these, you may replace this function with anything that works for you
|
||||
u32 LoadKeyDb(const char* path_db, AesKeyInfo* keydb, u32 bsize) {
|
||||
UINT fsize = 0;
|
||||
|
||||
|
||||
if (path_db) {
|
||||
if (fvx_qread (path_db, keydb, 0, bsize, &fsize) != FR_OK) fsize = 0;
|
||||
} else if (fvx_qread ("S:/" KEYDB_NAME, keydb, 0, bsize, &fsize) != FR_OK) {
|
||||
fsize = LoadSupportFile(KEYDB_NAME, keydb, bsize); // load key database support file
|
||||
}
|
||||
|
||||
|
||||
u32 nkeys = 0;
|
||||
if (fsize && !(fsize % sizeof(AesKeyInfo)))
|
||||
nkeys = fsize / sizeof(AesKeyInfo);
|
||||
@ -119,24 +119,24 @@ u32 LoadKeyFromFile(void* key, u32 keyslot, char type, char* id)
|
||||
{
|
||||
u8 keystore[16] __attribute__((aligned(32))) = {0};
|
||||
bool found = false;
|
||||
|
||||
|
||||
// checking the obvious
|
||||
if ((keyslot >= 0x40) || ((type != 'X') && (type != 'Y') && (type != 'N') && (type != 'I')))
|
||||
return 1;
|
||||
|
||||
|
||||
// check if already loaded
|
||||
if (!key && !id && (CheckKeySlot(keyslot, type) == 0)) return 0;
|
||||
|
||||
|
||||
// use keystore if key == NULL
|
||||
if (!key) key = keystore;
|
||||
|
||||
|
||||
// try to get key from 'aeskeydb.bin' file
|
||||
AesKeyInfo* keydb = (AesKeyInfo*) malloc(STD_BUFFER_SIZE);
|
||||
u32 nkeys = (keydb) ? LoadKeyDb(NULL, keydb, STD_BUFFER_SIZE) : 0;
|
||||
|
||||
|
||||
for (u32 i = 0; i < nkeys; i++) {
|
||||
AesKeyInfo* info = &(keydb[i]);
|
||||
if (!((info->slot == keyslot) && (info->type == type) &&
|
||||
if (!((info->slot == keyslot) && (info->type == type) &&
|
||||
((!id && !(info->id[0])) || (id && (strncmp(id, info->id, 10) == 0))) &&
|
||||
(!info->keyUnitType || (info->keyUnitType == GetUnitKeysType()))))
|
||||
continue;
|
||||
@ -146,9 +146,9 @@ u32 LoadKeyFromFile(void* key, u32 keyslot, char type, char* id)
|
||||
memcpy(key, info->key, 16);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
free(keydb);
|
||||
|
||||
|
||||
// load legacy slot0x??Key?.bin file instead
|
||||
if (!found && (type != 'I')) {
|
||||
char fname[64];
|
||||
@ -156,13 +156,13 @@ u32 LoadKeyFromFile(void* key, u32 keyslot, char type, char* id)
|
||||
(type == 'X') ? "X" : (type == 'Y') ? "Y" : (type == 'I') ? "IV" : "", (id) ? id : "");
|
||||
found = (LoadSupportFile(fname, key, 16) == 16);
|
||||
}
|
||||
|
||||
|
||||
// key still not found (duh)
|
||||
if (!found) return 1; // out of options here
|
||||
|
||||
|
||||
// done if this is an IV
|
||||
if (type == 'I') return 0;
|
||||
|
||||
|
||||
// now, setup the key
|
||||
if (type == 'X') {
|
||||
setup_aeskeyX(keyslot, key);
|
||||
@ -177,7 +177,7 @@ u32 LoadKeyFromFile(void* key, u32 keyslot, char type, char* id)
|
||||
keyYState |= 1ull << keyslot;
|
||||
}
|
||||
use_aeskey(keyslot);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -186,11 +186,11 @@ u32 InitKeyDb(const char* path)
|
||||
// use this to quickly initialize all applicable keys in aeskeydb.bin
|
||||
static const u64 keyslot_whitelist = (1ull<<0x02)|(1ull<<0x03)|(1ull<<0x05)|(1ull<<0x18)|(1ull<<0x19)|(1ull<<0x1A)|(1ull<<0x1B)|
|
||||
(1ull<<0x1C)|(1ull<<0x1D)|(1ull<<0x1E)|(1ull<<0x1F)|(1ull<<0x24)|(1ull<<0x25)|(1ull<<0x2F);
|
||||
|
||||
|
||||
// try to load aeskeydb.bin file
|
||||
AesKeyInfo* keydb = (AesKeyInfo*) malloc(STD_BUFFER_SIZE);
|
||||
u32 nkeys = (keydb) ? LoadKeyDb(path, keydb, STD_BUFFER_SIZE) : 0;
|
||||
|
||||
|
||||
// apply all applicable keys
|
||||
for (u32 i = 0; i < nkeys; i++) {
|
||||
AesKeyInfo* info = &(keydb[i]);
|
||||
@ -202,7 +202,7 @@ u32 InitKeyDb(const char* path)
|
||||
if ((info->type == 'I') || (*(info->id)) || (info->keyUnitType && (info->keyUnitType != GetUnitKeysType())) ||
|
||||
(CheckKeySlot(info->slot, info->type) == 0)) continue; // most likely valid, but not applicable or already set
|
||||
if (info->isEncrypted) CryptAesKeyInfo(info); // decrypt key
|
||||
|
||||
|
||||
// apply key
|
||||
u8 key[16] __attribute__((aligned(32))) = {0};
|
||||
char type = info->type;
|
||||
@ -222,7 +222,7 @@ u32 InitKeyDb(const char* path)
|
||||
}
|
||||
use_aeskey(keyslot);
|
||||
}
|
||||
|
||||
|
||||
free(keydb);
|
||||
return (nkeys) ? 0 : 1;
|
||||
}
|
||||
@ -236,15 +236,15 @@ u32 CheckRecommendedKeyDb(const char* path)
|
||||
0x40, 0x76, 0x54, 0x3D, 0xA3, 0xFF, 0x91, 0x1C, 0xE1, 0xCC, 0x4E, 0xC7, 0x2F, 0x92, 0xE4, 0xB7,
|
||||
0x2B, 0x24, 0x00, 0x15, 0xBE, 0x9B, 0xFC, 0xDE, 0x7F, 0xED, 0x95, 0x1D, 0xD5, 0xAB, 0x2D, 0xCB
|
||||
};
|
||||
|
||||
|
||||
// try to load aeskeydb.bin file
|
||||
AesKeyInfo* keydb = (AesKeyInfo*) malloc(STD_BUFFER_SIZE);
|
||||
if (!keydb) return 1;
|
||||
u32 nkeys = LoadKeyDb(path, keydb, STD_BUFFER_SIZE);
|
||||
|
||||
|
||||
// compare with recommended SHA
|
||||
bool res = (nkeys && (sha_cmp(recommended_sha, keydb, nkeys * sizeof(AesKeyInfo), SHA256_MODE) == 0));
|
||||
|
||||
|
||||
free(keydb);
|
||||
return res ? 0 : 1;
|
||||
}
|
||||
|
@ -14,10 +14,10 @@
|
||||
#define KEYS_UNKNOWN 0
|
||||
#define KEYS_DEVKIT 1
|
||||
#define KEYS_RETAIL 2
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
u8 slot; // keyslot, 0x00...0x3F
|
||||
u8 slot; // keyslot, 0x00...0x3F
|
||||
char type; // type 'X' / 'Y' / 'N' for normalKey / 'I' for IV
|
||||
char id[10]; // key ID for special keys, all zero for standard keys
|
||||
u8 reserved[2]; // reserved space
|
||||
|
@ -13,9 +13,9 @@ void sha_init(u32 mode)
|
||||
}
|
||||
|
||||
void sha_update(const void* src, u32 size)
|
||||
{
|
||||
{
|
||||
const u32* src32 = (const u32*)src;
|
||||
|
||||
|
||||
while(size >= 0x40) {
|
||||
while(*REG_SHACNT & 1);
|
||||
*((volatile _sha_block*)REG_SHAINFIFO) = *((const _sha_block*)src32);
|
||||
|
@ -63,7 +63,7 @@ FATpartition DriveInfo[13] = {
|
||||
{ TYPE_RAMDRV, SUBTYPE_NONE, 0, 0, 0xFF } // Z - RAMDRIVE
|
||||
};
|
||||
|
||||
static BYTE imgnand_mode = 0x00;
|
||||
static BYTE imgnand_mode = 0x00;
|
||||
|
||||
|
||||
|
||||
@ -81,7 +81,7 @@ DWORD get_fattime( void ) {
|
||||
((DSTIMEGET(&dstime, bcd_D)&0x1F) << 16) |
|
||||
((DSTIMEGET(&dstime, bcd_M)&0x0F) << 21) |
|
||||
(((DSTIMEGET(&dstime, bcd_Y)+(2000-1980))&0x7F) << 25);
|
||||
|
||||
|
||||
return fattime;
|
||||
}
|
||||
|
||||
@ -113,10 +113,10 @@ DSTATUS disk_initialize (
|
||||
imgnand_mode = (GetMountState() & IMG_NAND) ? 0x01 : 0x00;
|
||||
FATpartition* fat_info = PART_INFO(pdrv);
|
||||
BYTE type = PART_TYPE(pdrv);
|
||||
|
||||
|
||||
fat_info->offset = fat_info->size = 0;
|
||||
fat_info->keyslot = 0xFF;
|
||||
|
||||
|
||||
if (type == TYPE_SDCARD) {
|
||||
if (sdmmc_sdcard_init() != 0) return STA_NOINIT|STA_NODISK;
|
||||
fat_info->size = getMMCDevice(1)->total_size;
|
||||
@ -149,7 +149,7 @@ DSTATUS disk_initialize (
|
||||
InitRamDrive();
|
||||
fat_info->size = (GetRamDriveSize() + 0x1FF) / 0x200;
|
||||
}
|
||||
|
||||
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
@ -166,9 +166,9 @@ DRESULT disk_read (
|
||||
DWORD sector, /* Sector address in LBA */
|
||||
UINT count /* Number of sectors to read */
|
||||
)
|
||||
{
|
||||
{
|
||||
BYTE type = PART_TYPE(pdrv);
|
||||
|
||||
|
||||
if (type == TYPE_NONE) {
|
||||
return RES_PARERR;
|
||||
} else if (type == TYPE_SDCARD) {
|
||||
@ -205,7 +205,7 @@ DRESULT disk_write (
|
||||
)
|
||||
{
|
||||
BYTE type = PART_TYPE(pdrv);
|
||||
|
||||
|
||||
if (type == TYPE_NONE) {
|
||||
return RES_PARERR;
|
||||
} else if (type == TYPE_SDCARD) {
|
||||
@ -245,7 +245,7 @@ DRESULT disk_ioctl (
|
||||
{
|
||||
BYTE type = PART_TYPE(pdrv);
|
||||
FATpartition* fat_info = PART_INFO(pdrv);
|
||||
|
||||
|
||||
switch (cmd) {
|
||||
case GET_SECTOR_SIZE:
|
||||
*((DWORD*) buff) = 0x200;
|
||||
@ -262,7 +262,7 @@ DRESULT disk_ioctl (
|
||||
// nothing else to do here - sdmmc.c handles the rest
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
|
||||
return RES_PARERR;
|
||||
}
|
||||
#endif
|
||||
|
@ -5,7 +5,7 @@ u32 ValidateMbrHeader(MbrHeader* mbr) {
|
||||
u32 sector = 1; // check partitions
|
||||
for (u32 i = 0; i < 4; i++) {
|
||||
MbrPartitionInfo* partition = mbr->partitions + i;
|
||||
if (!partition->count && i) continue;
|
||||
if (!partition->count && i) continue;
|
||||
else if (!partition->count) return 1; // first partition can't be empty
|
||||
if ((partition->type != 0x1) && (partition->type != 0x4) && (partition->type != 0x6) &&
|
||||
(partition->type != 0xB) && (partition->type != 0xC) && (partition->type != 0xE))
|
||||
@ -22,7 +22,7 @@ u32 ValidateFatHeader(void* fat) {
|
||||
if (strncmp(fat32->fs_type, "FAT32 ", 8) == 0)
|
||||
return 0; // is FAT32 header
|
||||
Fat16Header* fat16 = (Fat16Header*) fat;
|
||||
if ((strncmp(fat16->fs_type, "FAT16 ", 8) == 0) ||
|
||||
if ((strncmp(fat16->fs_type, "FAT16 ", 8) == 0) ||
|
||||
(strncmp(fat16->fs_type, "FAT12 ", 8) == 0) ||
|
||||
(strncmp(fat16->fs_type, "FAT ", 8) == 0))
|
||||
return 0; // is FAT16 / FAT12 header
|
||||
|
@ -15,7 +15,7 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
char text[446];
|
||||
MbrPartitionInfo partitions[4];
|
||||
MbrPartitionInfo partitions[4];
|
||||
u16 magic; // 0xAA55
|
||||
} PACKED_ALIGN(2) MbrHeader;
|
||||
|
||||
@ -50,7 +50,7 @@ typedef struct {
|
||||
u32 clr_root; // 0x02
|
||||
u16 sct_fsinfo; // 0x01
|
||||
u16 sct_backup; // 0x06
|
||||
u8 reserved3[12];
|
||||
u8 reserved3[12];
|
||||
u8 ndrive; // 0x80
|
||||
u8 head_cur; // 0x00
|
||||
u8 boot_sig; // 0x29
|
||||
|
@ -112,7 +112,7 @@ u64 IdentifyFileType(const char* path) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (fsize == sizeof(TitleInfoEntry) && (strncasecmp(path, "T:/", 3) == 0)) {
|
||||
const char* mntpath = GetMountPath();
|
||||
if (mntpath && *mntpath) {
|
||||
@ -183,6 +183,6 @@ u64 IdentifyFileType(const char* path) {
|
||||
if (FileGetSize(path_cdn) > 0)
|
||||
return GAME_NUSCDN; // NUS/CDN type 1
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ static bool search_title_mode = false;
|
||||
int DriveType(const char* path) {
|
||||
int type = DRV_UNKNOWN;
|
||||
int pdrv = GetMountedFSNum(path);
|
||||
|
||||
|
||||
if (CheckAliasDrive(path)) {
|
||||
type = DRV_FAT | DRV_ALIAS | ((*path == 'A') ? DRV_SYSNAND : DRV_EMUNAND);
|
||||
} else if (*search_pattern && *search_path && (strncmp(path, "Z:", 3) == 0)) {
|
||||
@ -39,7 +39,7 @@ int DriveType(const char* path) {
|
||||
} else if ((pdrv >= 7) && (pdrv <= 9) &&
|
||||
(GetMountState() & (IMG_FAT|IMG_NAND))) {
|
||||
type = DRV_FAT | DRV_IMAGE | DRV_STDFAT;
|
||||
}
|
||||
}
|
||||
} else if (CheckVirtualDrive(path)) {
|
||||
int vsrc = GetVirtualSource(path);
|
||||
if (vsrc == VRT_SYSNAND) {
|
||||
@ -58,9 +58,9 @@ int DriveType(const char* path) {
|
||||
type = DRV_VIRTUAL | DRV_CART;
|
||||
} else if (vsrc == VRT_VRAM) {
|
||||
type = DRV_VIRTUAL | DRV_VRAM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
@ -82,14 +82,14 @@ bool GetRootDirContentsWorker(DirStruct* contents) {
|
||||
static const char* drvname[] = { FS_DRVNAME };
|
||||
static const char* drvnum[] = { FS_DRVNUM };
|
||||
u32 n_entries = 0;
|
||||
|
||||
|
||||
char sdlabel[DRV_LABEL_LEN];
|
||||
if (!GetFATVolumeLabel("0:", sdlabel) || !(*sdlabel))
|
||||
strcpy(sdlabel, "NOLABEL");
|
||||
|
||||
char carttype[16];
|
||||
GetVCartTypeString(carttype);
|
||||
|
||||
|
||||
// virtual root objects hacked in
|
||||
for (u32 i = 0; (i < countof(drvnum)) && (n_entries < MAX_DIR_ENTRIES); i++) {
|
||||
DirEntry* entry = &(contents->entry[n_entries]);
|
||||
@ -124,7 +124,7 @@ bool GetRootDirContentsWorker(DirStruct* contents) {
|
||||
n_entries++;
|
||||
}
|
||||
contents->n_entries = n_entries;
|
||||
|
||||
|
||||
return contents->n_entries;
|
||||
}
|
||||
|
||||
@ -133,11 +133,11 @@ bool GetDirContentsWorker(DirStruct* contents, char* fpath, int fnsize, const ch
|
||||
FILINFO fno;
|
||||
char* fname = fpath + strnlen(fpath, fnsize - 1);
|
||||
bool ret = false;
|
||||
|
||||
|
||||
if (fvx_opendir(&pdir, fpath) != FR_OK)
|
||||
return false;
|
||||
if (*(fname-1) != '/') *(fname++) = '/';
|
||||
|
||||
|
||||
while (fvx_readdir(&pdir, &fno) == FR_OK) {
|
||||
if ((strncmp(fno.fname, ".", 2) == 0) || (strncmp(fno.fname, "..", 3) == 0))
|
||||
continue; // filter out virtual entries
|
||||
@ -176,7 +176,7 @@ bool GetDirContentsWorker(DirStruct* contents, char* fpath, int fnsize, const ch
|
||||
}
|
||||
}
|
||||
fvx_closedir(&pdir);
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -222,7 +222,7 @@ uint64_t GetFreeSpace(const char* path)
|
||||
int pdrv = GetMountedFSNum(path);
|
||||
FATFS* fsobj = GetMountedFSObject(path);
|
||||
if ((pdrv < 0) || !fsobj) return 0;
|
||||
|
||||
|
||||
snprintf(fsname, 3, "%i:", pdrv);
|
||||
if (f_getfree(fsname, &free_clusters, &fsptr) != FR_OK)
|
||||
return 0;
|
||||
|
@ -40,7 +40,7 @@
|
||||
"MEMORY VIRTUAL", \
|
||||
"VRAM VIRTUAL", \
|
||||
"LAST SEARCH" \
|
||||
|
||||
|
||||
#define FS_DRVNUM \
|
||||
"0:", "1:", "2:", "3:", "A:", "S:", "4:", "5:", "6:", "B:", "E:", "7:", "8:", "9:", "I:", "C:", "G:", "K:", "T:", "D:", "M:", "V:", "Z:"
|
||||
|
||||
|
@ -25,7 +25,7 @@ bool GoodRenamer(DirEntry* entry, bool ask) {
|
||||
if ((GetGoodName(goodname, entry->path, false) != 0) ||
|
||||
(strncmp(goodname + strnlen(goodname, 256) - 4, ".tmd", 4) == 0)) // no TMD, please
|
||||
return false;
|
||||
|
||||
|
||||
if (ask) { // ask for confirmatiom
|
||||
char oldname_tr[32+1];
|
||||
char newname_ww[256];
|
||||
@ -35,7 +35,7 @@ bool GoodRenamer(DirEntry* entry, bool ask) {
|
||||
if (!ShowPrompt(true, "%s\nRename to good name?\n \n%s", oldname_tr, newname_ww))
|
||||
return true; // call it a success because user choice
|
||||
}
|
||||
|
||||
|
||||
char npath[256]; // get new path
|
||||
strncpy(npath, entry->path, 256);
|
||||
char* nname = strrchr(npath, '/');
|
||||
@ -47,6 +47,6 @@ bool GoodRenamer(DirEntry* entry, bool ask) {
|
||||
if (f_rename(entry->path, npath) != FR_OK) return false;
|
||||
strncpy(entry->path, npath, 256);
|
||||
entry->name = entry->path + (nname - npath);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ bool InitSDCardFS() {
|
||||
|
||||
bool InitExtFS() {
|
||||
static bool ramdrv_ready = false;
|
||||
|
||||
|
||||
for (u32 i = 1; i < NORM_FS; i++) {
|
||||
char fsname[8];
|
||||
snprintf(fsname, 7, "%lu:", i);
|
||||
|
@ -6,7 +6,7 @@
|
||||
// init SD card filesystem - required(?) for everything else
|
||||
bool InitSDCardFS();
|
||||
|
||||
// init fill external fileystem
|
||||
// init fill external fileystem
|
||||
bool InitExtFS();
|
||||
|
||||
// mount and init image file system
|
||||
@ -21,7 +21,7 @@ void DeinitSDCardFS();
|
||||
// dismount drives of a certain type
|
||||
void DismountDriveType(u32 type);
|
||||
|
||||
// returns the mount state of the SD card
|
||||
// returns the mount state of the SD card
|
||||
bool CheckSDMountState(void);
|
||||
|
||||
// get number of mounted file system (only for FATFS filesystems)
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include "ui.h"
|
||||
#include "sdmmc.h"
|
||||
|
||||
#define PATH_SYS_LVL1 "S:/twln.bin", "S:/twlp.bin"
|
||||
#define PATH_SYS_LVL1 "S:/twln.bin", "S:/twlp.bin"
|
||||
#define PATH_SYS_LVL2 "1:/rw/sys/LocalFriendCodeSeed_B", "1:/rw/sys/LocalFriendCodeSeed_A", \
|
||||
"1:/rw/sys/SecureInfo_A", "1:/rw/sys/SecureInfo_B", \
|
||||
"1:/private/movable.sed", "1:/ro/sys/HWCAL0.dat", "1:/ro/sys/HWCAL1.dat", \
|
||||
@ -23,7 +23,7 @@ bool CheckWritePermissions(const char* path) {
|
||||
char area_name[16];
|
||||
int drvtype = DriveType(path);
|
||||
u32 perm;
|
||||
|
||||
|
||||
// create a standardized path string
|
||||
char path_f[256];
|
||||
char* p = (char*) path;
|
||||
@ -37,13 +37,13 @@ bool CheckWritePermissions(const char* path) {
|
||||
// check mounted image write permissions
|
||||
if ((drvtype & DRV_IMAGE) && !CheckWritePermissions(GetMountPath()))
|
||||
return false; // endless loop when mounted file inside image, but not possible
|
||||
|
||||
|
||||
// SD card write protection check
|
||||
if ((drvtype & (DRV_SDCARD | DRV_EMUNAND | DRV_ALIAS)) && SD_WRITE_PROTECTED) {
|
||||
ShowPrompt(false, "SD card is write protected!\nCan't continue.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// check drive type, get permission type
|
||||
if (drvtype & DRV_SYSNAND) {
|
||||
static const u32 perms[] = { PERM_SYS_LVL0, PERM_SYS_LVL1, PERM_SYS_LVL2, PERM_SYS_LVL3 };
|
||||
@ -104,20 +104,20 @@ bool CheckWritePermissions(const char* path) {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// check permission, return if already set
|
||||
if ((write_permissions & perm) == perm)
|
||||
return true;
|
||||
|
||||
|
||||
// offer unlock if possible
|
||||
if (!(perm & (PERM_VRAM|PERM_GAME|PERM_XORPAD))) {
|
||||
// ask the user
|
||||
if (!ShowPrompt(true, "Writing to %s is locked!\nUnlock it now?", area_name))
|
||||
return false;
|
||||
|
||||
|
||||
return SetWritePermissions(perm, true);
|
||||
}
|
||||
|
||||
|
||||
// unlock not possible
|
||||
ShowPrompt(false, "Unlock write permission for\n%s is not allowed.", area_name);
|
||||
return false;
|
||||
@ -141,7 +141,7 @@ bool SetWritePermissions(u32 perm, bool add_perm) {
|
||||
if (!add_perm) write_permissions = perm;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
switch (perm) {
|
||||
case PERM_BASE:
|
||||
if (!ShowUnlockSequence(1, "You want to enable base\nwriting permissions."))
|
||||
@ -207,9 +207,9 @@ bool SetWritePermissions(u32 perm, bool add_perm) {
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
write_permissions = add_perm ? write_permissions | perm : perm;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -43,19 +43,19 @@ bool FormatSDCard(u64 hidden_mb, u32 cluster_size, const char* label) {
|
||||
u32 emu_size = (u32) ((hidden_mb * 1024 * 1024) / 512);
|
||||
u32 fat_sector = align(emu_sector + emu_size, 0x2000); // align to 4MB
|
||||
u32 fat_size = (fat_sector < sd_size) ? sd_size - fat_sector : 0;
|
||||
|
||||
|
||||
// FAT size check
|
||||
if (fat_size < 0x80000) { // minimum free space: 256MB
|
||||
ShowPrompt(false, "Error: SD card is too small");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Write protection check
|
||||
if (SD_WRITE_PROTECTED) {
|
||||
ShowPrompt(false, "SD card is write protected!\nCan't continue.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// build the MBR
|
||||
memcpy(mbrdata + 0x08, &fat_sector, 4);
|
||||
memcpy(mbrdata + 0x0C, &fat_size, 4);
|
||||
@ -64,13 +64,13 @@ bool FormatSDCard(u64 hidden_mb, u32 cluster_size, const char* label) {
|
||||
memcpy(mbr + 0x1BE, mbrdata, 0x42);
|
||||
if (hidden_mb) memcpy(mbr, "GATEWAYNAND", 12); // legacy
|
||||
else memset(mbr + 0x1CE, 0, 0x10);
|
||||
|
||||
|
||||
// one last warning....
|
||||
// 0:/Nintendo 3DS/ write permission is ignored here, this warning is enough
|
||||
if (!ShowUnlockSequence(5, "!WARNING!\n \nProceeding will format this SD.\nThis will irreversibly delete\nALL data on it."))
|
||||
return false;
|
||||
ShowString("Formatting SD, please wait...");
|
||||
|
||||
ShowString("Formatting SD, please wait...");
|
||||
|
||||
// write the MBR to disk
|
||||
// !this assumes a fully deinitialized file system!
|
||||
if ((sdmmc_sdcard_init() != 0) || (sdmmc_sdcard_writesectors(0, 1, mbr) != 0) ||
|
||||
@ -78,11 +78,11 @@ bool FormatSDCard(u64 hidden_mb, u32 cluster_size, const char* label) {
|
||||
ShowPrompt(false, "Error: SD card i/o failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// format the SD card
|
||||
VolToPart[0].pt = 1; // workaround to prevent FatFS rebuilding the MBR
|
||||
InitSDCardFS();
|
||||
|
||||
|
||||
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
||||
if (!buffer) bkpt; // will not happen
|
||||
MKFS_PARM opt0, opt1;
|
||||
@ -92,14 +92,14 @@ bool FormatSDCard(u64 hidden_mb, u32 cluster_size, const char* label) {
|
||||
opt0.align = opt1.align = 0;
|
||||
opt0.n_fat = opt1.n_fat = 1;
|
||||
opt0.n_root = opt1.n_root = 0;
|
||||
bool ret = ((f_mkfs("0:", &opt0, buffer, STD_BUFFER_SIZE) == FR_OK) ||
|
||||
bool ret = ((f_mkfs("0:", &opt0, buffer, STD_BUFFER_SIZE) == FR_OK) ||
|
||||
(f_mkfs("0:", &opt1, buffer, STD_BUFFER_SIZE) == FR_OK)) &&
|
||||
(f_setlabel((label) ? label : "0:GM9SD") == FR_OK);
|
||||
free(buffer);
|
||||
|
||||
|
||||
DeinitSDCardFS();
|
||||
VolToPart[0].pt = 0; // revert workaround to prevent SD mount problems
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -108,12 +108,12 @@ bool SetupBonusDrive(void) {
|
||||
return false;
|
||||
ShowString("Formatting drive, please wait...");
|
||||
if (GetMountState() & IMG_NAND) InitImgFS(NULL);
|
||||
|
||||
|
||||
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
||||
if (!buffer) bkpt;
|
||||
bool ret = (f_mkfs("8:", NULL, buffer, STD_BUFFER_SIZE) == FR_OK);
|
||||
free(buffer);
|
||||
|
||||
|
||||
if (ret) {
|
||||
f_setlabel("8:BONUS");
|
||||
InitExtFS();
|
||||
@ -124,12 +124,12 @@ bool SetupBonusDrive(void) {
|
||||
bool FileUnlock(const char* path) {
|
||||
FIL file;
|
||||
FRESULT res;
|
||||
|
||||
|
||||
if (!(DriveType(path) & DRV_FAT)) return true; // can't really check this
|
||||
if ((res = fx_open(&file, path, FA_READ | FA_OPEN_EXISTING)) != FR_OK) {
|
||||
char pathstr[32 + 1];
|
||||
TruncateString(pathstr, path, 32, 8);
|
||||
if (GetMountState() && (res == FR_LOCKED) &&
|
||||
if (GetMountState() && (res == FR_LOCKED) &&
|
||||
(ShowPrompt(true, "%s\nFile is currently mounted.\nUnmount to unlock?", pathstr))) {
|
||||
InitImgFS(NULL);
|
||||
if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
@ -137,7 +137,7 @@ bool FileUnlock(const char* path) {
|
||||
} else return false;
|
||||
}
|
||||
fx_close(&file);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -165,19 +165,19 @@ bool FileGetSha256(const char* path, u8* sha256, u64 offset, u64 size) {
|
||||
bool ret = true;
|
||||
FIL file;
|
||||
u64 fsize;
|
||||
|
||||
|
||||
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return false;
|
||||
|
||||
|
||||
fsize = fvx_size(&file);
|
||||
if (offset + size > fsize) return false;
|
||||
if (!size) size = fsize - offset;
|
||||
fvx_lseek(&file, offset);
|
||||
|
||||
|
||||
u32 bufsiz = min(STD_BUFFER_SIZE, fsize);
|
||||
u8* buffer = (u8*) malloc(bufsiz);
|
||||
if (!buffer) return false;
|
||||
|
||||
|
||||
ShowProgress(0, 0, path);
|
||||
sha_init(SHA256_MODE);
|
||||
for (u64 pos = 0; (pos < size) && ret; pos += bufsiz) {
|
||||
@ -189,13 +189,13 @@ bool FileGetSha256(const char* path, u8* sha256, u64 offset, u64 size) {
|
||||
ret = false;
|
||||
sha_update(buffer, bytes_read);
|
||||
}
|
||||
|
||||
|
||||
sha_get(sha256);
|
||||
fvx_close(&file);
|
||||
free(buffer);
|
||||
|
||||
|
||||
ShowProgress(1, 1, path);
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -203,13 +203,13 @@ u32 FileFindData(const char* path, u8* data, u32 size_data, u32 offset_file) {
|
||||
FIL file; // used for FAT & virtual
|
||||
u64 found = (u64) -1;
|
||||
u64 fsize = FileGetSize(path);
|
||||
|
||||
|
||||
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return found;
|
||||
|
||||
|
||||
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
||||
if (!buffer) return false;
|
||||
|
||||
|
||||
// main routine
|
||||
for (u32 pass = 0; pass < 2; pass++) {
|
||||
bool show_progress = false;
|
||||
@ -236,10 +236,10 @@ u32 FileFindData(const char* path, u8* data, u32 size_data, u32 offset_file) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
free(buffer);
|
||||
fvx_close(&file);
|
||||
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
@ -247,13 +247,13 @@ bool FileInjectFile(const char* dest, const char* orig, u64 off_dest, u64 off_or
|
||||
FIL ofile;
|
||||
FIL dfile;
|
||||
bool allow_expand = (flags && (*flags & ALLOW_EXPAND));
|
||||
|
||||
|
||||
if (!CheckWritePermissions(dest)) return false;
|
||||
if (strncasecmp(dest, orig, 256) == 0) {
|
||||
ShowPrompt(false, "Error: Can't inject file into itself");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// open destination / origin
|
||||
if (fvx_open(&dfile, dest, FA_WRITE | ((allow_expand) ? FA_OPEN_ALWAYS : FA_OPEN_EXISTING)) != FR_OK)
|
||||
return false;
|
||||
@ -266,7 +266,7 @@ bool FileInjectFile(const char* dest, const char* orig, u64 off_dest, u64 off_or
|
||||
fvx_lseek(&ofile, off_orig);
|
||||
if (!size && (off_orig < fvx_size(&ofile)))
|
||||
size = fvx_size(&ofile) - off_orig;
|
||||
|
||||
|
||||
// check file limits
|
||||
if (!allow_expand && (off_dest + size > fvx_size(&dfile))) {
|
||||
ShowPrompt(false, "Operation would write beyond end of file");
|
||||
@ -279,10 +279,10 @@ bool FileInjectFile(const char* dest, const char* orig, u64 off_dest, u64 off_or
|
||||
fvx_close(&ofile);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
||||
if (!buffer) return false;
|
||||
|
||||
|
||||
bool ret = true;
|
||||
ShowProgress(0, 0, orig);
|
||||
for (u64 pos = 0; (pos < size) && ret; pos += STD_BUFFER_SIZE) {
|
||||
@ -302,39 +302,39 @@ bool FileInjectFile(const char* dest, const char* orig, u64 off_dest, u64 off_or
|
||||
}
|
||||
}
|
||||
ShowProgress(1, 1, orig);
|
||||
|
||||
|
||||
free(buffer);
|
||||
fvx_close(&dfile);
|
||||
fvx_close(&ofile);
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileSetByte(const char* dest, u64 offset, u64 size, u8 fillbyte, u32* flags) {
|
||||
FIL dfile;
|
||||
bool allow_expand = (flags && (*flags & ALLOW_EXPAND));
|
||||
|
||||
|
||||
if (!CheckWritePermissions(dest)) return false;
|
||||
|
||||
|
||||
// open destination
|
||||
if (fvx_open(&dfile, dest, FA_WRITE | ((allow_expand) ? FA_OPEN_ALWAYS : FA_OPEN_EXISTING)) != FR_OK)
|
||||
return false;
|
||||
fvx_lseek(&dfile, offset);
|
||||
if (!size && (offset < fvx_size(&dfile)))
|
||||
size = fvx_size(&dfile) - offset;
|
||||
|
||||
|
||||
// check file limits
|
||||
if (!allow_expand && (offset + size > fvx_size(&dfile))) {
|
||||
ShowPrompt(false, "Operation would write beyond end of file");
|
||||
fvx_close(&dfile);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
u32 bufsiz = min(STD_BUFFER_SIZE, size);
|
||||
u8* buffer = (u8*) malloc(bufsiz);
|
||||
if (!buffer) return false;
|
||||
memset(buffer, fillbyte, bufsiz);
|
||||
|
||||
|
||||
bool ret = true;
|
||||
ShowProgress(0, 0, dest);
|
||||
for (u64 pos = 0; (pos < size) && ret; pos += bufsiz) {
|
||||
@ -352,10 +352,10 @@ bool FileSetByte(const char* dest, u64 offset, u64 size, u8 fillbyte, u32* flags
|
||||
}
|
||||
}
|
||||
ShowProgress(1, 1, dest);
|
||||
|
||||
|
||||
free(buffer);
|
||||
fvx_close(&dfile);
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -364,7 +364,7 @@ bool FileCreateDummy(const char* cpath, const char* filename, u64 size) {
|
||||
if (!CheckWritePermissions(cpath)) return false;
|
||||
if (filename) snprintf(npath, 255, "%s/%s", cpath, filename);
|
||||
else snprintf(npath, 255, "%s", cpath);
|
||||
|
||||
|
||||
// create dummy file (fail if already existing)
|
||||
// then, expand the file size via cluster preallocation
|
||||
FIL dfile;
|
||||
@ -373,7 +373,7 @@ bool FileCreateDummy(const char* cpath, const char* filename, u64 size) {
|
||||
f_lseek(&dfile, size > 0xFFFFFFFF ? 0xFFFFFFFF : (FSIZE_t) size);
|
||||
f_sync(&dfile);
|
||||
fx_close(&dfile);
|
||||
|
||||
|
||||
return (fa_stat(npath, NULL) == FR_OK);
|
||||
}
|
||||
|
||||
@ -425,7 +425,7 @@ bool DirInfoWorker(char* fpath, bool virtual, u64* tsize, u32* tdirs, u32* tfile
|
||||
}
|
||||
f_closedir(&pdir);
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -449,20 +449,20 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer,
|
||||
bool append = (flags && (*flags & APPEND_ALL));
|
||||
bool calcsha = (flags && (*flags & CALC_SHA) && !append);
|
||||
bool ret = false;
|
||||
|
||||
|
||||
// check destination write permission (special paths only)
|
||||
if (((*dest == '1') || (strncmp(dest, "0:/Nintendo 3DS", 16) == 0)) &&
|
||||
(!flags || !(*flags & OVERRIDE_PERM)) &&
|
||||
(!flags || !(*flags & OVERRIDE_PERM)) &&
|
||||
!CheckWritePermissions(dest)) return false;
|
||||
|
||||
|
||||
FILINFO fno;
|
||||
if (fvx_stat(orig, &fno) != FR_OK) return false; // origin does not exist
|
||||
if (move && (to_virtual || fno.fattrib & AM_VRT)) return false; // trying to move a virtual file
|
||||
|
||||
|
||||
// path string (for output)
|
||||
char deststr[36 + 1];
|
||||
TruncateString(deststr, dest, 36, 8);
|
||||
|
||||
|
||||
// the copy process takes place here
|
||||
if (!ShowProgress(0, 0, orig) && !(flags && (*flags & NO_CANCEL))) {
|
||||
if (ShowPrompt(true, "%s\nB button detected. Cancel?", deststr)) return false;
|
||||
@ -473,12 +473,12 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer,
|
||||
} else if (fno.fattrib & AM_DIR) { // processing folders (same for move & copy)
|
||||
DIR pdir;
|
||||
char* fname = orig + strnlen(orig, 256);
|
||||
|
||||
|
||||
if (append) {
|
||||
if (!silent) ShowPrompt(false, "%s\nError: Cannot append a folder", deststr);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// create the destination folder if it does not already exist
|
||||
if (fvx_opendir(&pdir, dest) != FR_OK) {
|
||||
if (fvx_mkdir(dest) != FR_OK) {
|
||||
@ -486,11 +486,11 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer,
|
||||
return false;
|
||||
}
|
||||
} else fvx_closedir(&pdir);
|
||||
|
||||
|
||||
if (fvx_opendir(&pdir, orig) != FR_OK)
|
||||
return false;
|
||||
*(fname++) = '/';
|
||||
|
||||
|
||||
while (fvx_readdir(&pdir, &fno) == FR_OK) {
|
||||
if ((strncmp(fno.fname, ".", 2) == 0) || (strncmp(fno.fname, "..", 3) == 0))
|
||||
continue; // filter out virtual entries
|
||||
@ -509,7 +509,7 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer,
|
||||
if (!res) break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fvx_closedir(&pdir);
|
||||
*(--fname) = '\0';
|
||||
} else if (move) { // moving if destination exists
|
||||
@ -525,20 +525,20 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer,
|
||||
FIL dfile;
|
||||
u64 osize;
|
||||
u64 dsize;
|
||||
|
||||
|
||||
if (fvx_open(&ofile, orig, FA_READ | FA_OPEN_EXISTING) != FR_OK) {
|
||||
if (!FileUnlock(orig) || (fvx_open(&ofile, orig, FA_READ | FA_OPEN_EXISTING) != FR_OK))
|
||||
return false;
|
||||
ShowProgress(0, 0, orig); // reinit progress bar
|
||||
}
|
||||
|
||||
|
||||
if ((!append || (fvx_open(&dfile, dest, FA_WRITE | FA_OPEN_EXISTING) != FR_OK)) &&
|
||||
(fvx_open(&dfile, dest, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK)) {
|
||||
if (!silent) ShowPrompt(false, "%s\nError: Cannot open destination file", deststr);
|
||||
fvx_close(&ofile);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
ret = true; // destination file exists by now, so we need to handle deletion
|
||||
osize = fvx_size(&ofile);
|
||||
dsize = append ? fvx_size(&dfile) : 0; // always 0 if not appending to file
|
||||
@ -546,16 +546,16 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer,
|
||||
if (!silent) ShowPrompt(false, "%s\nError: Not enough space available", deststr);
|
||||
ret = false;
|
||||
}
|
||||
|
||||
|
||||
fvx_lseek(&dfile, dsize);
|
||||
fvx_sync(&dfile);
|
||||
fvx_lseek(&ofile, 0);
|
||||
fvx_sync(&ofile);
|
||||
|
||||
|
||||
if (calcsha) sha_init(SHA256_MODE);
|
||||
for (u64 pos = 0; (pos < osize) && ret; pos += bufsiz) {
|
||||
UINT bytes_read = 0;
|
||||
UINT bytes_written = 0;
|
||||
UINT bytes_written = 0;
|
||||
if ((fvx_read(&ofile, buffer, bufsiz, &bytes_read) != FR_OK) ||
|
||||
(fvx_write(&dfile, buffer, bytes_read, &bytes_written) != FR_OK) ||
|
||||
(bytes_read != bytes_written))
|
||||
@ -574,7 +574,7 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer,
|
||||
sha_update(buffer, bytes_read);
|
||||
}
|
||||
ShowProgress(1, 1, orig);
|
||||
|
||||
|
||||
fvx_close(&ofile);
|
||||
fvx_close(&dfile);
|
||||
if (!ret && ((dsize == 0) || (fvx_lseek(&dfile, dsize) != FR_OK) || (f_truncate(&dfile) != FR_OK))) {
|
||||
@ -587,7 +587,7 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer,
|
||||
FileSetData(dest, sha256, 0x20, 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -597,10 +597,10 @@ bool PathMoveCopy(const char* dest, const char* orig, u32* flags, bool move) {
|
||||
if (!CheckWritePermissions(dest)) return false;
|
||||
if (move && !CheckDirWritePermissions(orig)) return false;
|
||||
}
|
||||
|
||||
|
||||
// reset local flags
|
||||
if (flags) *flags = *flags & ~(SKIP_CUR|OVERWRITE_CUR);
|
||||
|
||||
|
||||
// preparations
|
||||
int ddrvtype = DriveType(dest);
|
||||
int odrvtype = DriveType(orig);
|
||||
@ -610,32 +610,32 @@ bool PathMoveCopy(const char* dest, const char* orig, u32* flags, bool move) {
|
||||
strncpy(lorig, orig, 255);
|
||||
char deststr[36 + 1];
|
||||
TruncateString(deststr, ldest, 36, 8);
|
||||
|
||||
|
||||
// moving only for regular FAT drives (= not alias drives)
|
||||
if (move && !(ddrvtype & odrvtype & DRV_STDFAT)) {
|
||||
ShowPrompt(false, "Error: Only FAT files can be moved");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// is destination part of origin?
|
||||
u32 olen = strnlen(lorig, 255);
|
||||
if ((strncasecmp(ldest, lorig, olen) == 0) && (ldest[olen] == '/')) {
|
||||
ShowPrompt(false, "%s\nError: Destination is part of origin", deststr);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!(ddrvtype & DRV_VIRTUAL)) { // FAT destination handling
|
||||
// get destination name
|
||||
char* dname = strrchr(ldest, '/');
|
||||
if (!dname) return false;
|
||||
dname++;
|
||||
|
||||
|
||||
// check & fix destination == origin
|
||||
while (strncasecmp(ldest, lorig, 255) == 0) {
|
||||
if (!ShowKeyboardOrPrompt(dname, 255 - (dname - ldest), "%s\nDestination equals origin\nChoose another name?", deststr))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// check if destination exists
|
||||
if (flags && !(*flags & (OVERWRITE_CUR|OVERWRITE_ALL|APPEND_ALL)) && (fa_stat(ldest, NULL) == FR_OK)) {
|
||||
if (*flags & SKIP_ALL) {
|
||||
@ -665,29 +665,29 @@ bool PathMoveCopy(const char* dest, const char* orig, u32* flags, bool move) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ensure the destination path exists
|
||||
if (flags && (*flags & BUILD_PATH)) fvx_rmkpath(ldest);
|
||||
|
||||
|
||||
// setup buffer
|
||||
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
||||
if (!buffer) {
|
||||
ShowPrompt(false, "Out of memory.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// actual move / copy operation
|
||||
bool same_drv = (strncasecmp(lorig, ldest, 2) == 0);
|
||||
bool res = PathMoveCopyRec(ldest, lorig, flags, move && same_drv, buffer, STD_BUFFER_SIZE);
|
||||
if (move && res && (!flags || !(*flags&SKIP_CUR))) PathDelete(lorig);
|
||||
|
||||
|
||||
free(buffer);
|
||||
return res;
|
||||
} else { // virtual destination handling
|
||||
// can't write an SHA file to a virtual destination
|
||||
if (flags) *flags &= ~CALC_SHA;
|
||||
bool force_unmount = false;
|
||||
|
||||
|
||||
// handle NAND image unmounts
|
||||
if ((ddrvtype & (DRV_SYSNAND|DRV_EMUNAND|DRV_IMAGE)) && !(GetVirtualSource(dest) & (VRT_DISADIFF | VRT_BDRI))) {
|
||||
FILINFO fno;
|
||||
@ -695,31 +695,31 @@ bool PathMoveCopy(const char* dest, const char* orig, u32* flags, bool move) {
|
||||
if ((fvx_stat(ldest, &fno) == FR_OK) && (fno.fsize > 4 * 1024 * 1024))
|
||||
force_unmount = true;
|
||||
}
|
||||
|
||||
|
||||
// prevent illegal operations
|
||||
if (force_unmount && (odrvtype & ddrvtype & (DRV_SYSNAND|DRV_EMUNAND|DRV_IMAGE))) {
|
||||
ShowPrompt(false, "Copy operation is not allowed");
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// check destination == origin
|
||||
if (strncasecmp(ldest, lorig, 255) == 0) {
|
||||
ShowPrompt(false, "%s\nDestination equals origin", deststr);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// setup buffer
|
||||
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
||||
if (!buffer) {
|
||||
ShowPrompt(false, "Out of memory.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// actual virtual copy operation
|
||||
if (force_unmount) DismountDriveType(DriveType(ldest)&(DRV_SYSNAND|DRV_EMUNAND|DRV_IMAGE));
|
||||
bool res = PathMoveCopyRec(ldest, lorig, flags, false, buffer, STD_BUFFER_SIZE);
|
||||
if (force_unmount) InitExtFS();
|
||||
|
||||
|
||||
free(buffer);
|
||||
return res;
|
||||
}
|
||||
@ -731,7 +731,7 @@ bool PathCopy(const char* destdir, const char* orig, u32* flags) {
|
||||
char* oname = strrchr(orig, '/');
|
||||
if (oname == NULL) return false; // not a proper origin path
|
||||
snprintf(dest, 255, "%s/%s", destdir, (++oname));
|
||||
|
||||
|
||||
// virtual destination special handling
|
||||
if (GetVirtualSource(destdir) & ~VRT_BDRI) {
|
||||
u64 osize = FileGetSize(orig);
|
||||
@ -762,7 +762,7 @@ bool PathCopy(const char* destdir, const char* orig, u32* flags) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return PathMoveCopy(dest, orig, flags, false);
|
||||
}
|
||||
|
||||
@ -772,7 +772,7 @@ bool PathMove(const char* destdir, const char* orig, u32* flags) {
|
||||
char* oname = strrchr(orig, '/');
|
||||
if (oname == NULL) return false; // not a proper origin path
|
||||
snprintf(dest, 255, "%s/%s", destdir, (++oname));
|
||||
|
||||
|
||||
return PathMoveCopy(dest, orig, flags, true);
|
||||
}
|
||||
|
||||
@ -784,13 +784,13 @@ bool PathDelete(const char* path) {
|
||||
bool PathRename(const char* path, const char* newname) {
|
||||
char npath[256]; // 256 is the maximum length of a full path
|
||||
char* oldname = strrchr(path, '/');
|
||||
|
||||
|
||||
if (!CheckDirWritePermissions(path)) return false;
|
||||
if (!oldname) return false;
|
||||
oldname++;
|
||||
strncpy(npath, path, oldname - path);
|
||||
strncpy(npath + (oldname - path), newname, 255 - (oldname - path));
|
||||
|
||||
|
||||
if (fvx_rename(path, npath) != FR_OK) return false;
|
||||
if ((strncasecmp(path, npath, 256) != 0) &&
|
||||
((fvx_stat(path, NULL) == FR_OK) || (fvx_stat(npath, NULL) != FR_OK)))
|
||||
@ -808,18 +808,18 @@ bool FileSelectorWorker(char* result, const char* text, const char* path, const
|
||||
char path_local[256];
|
||||
strncpy(path_local, path, 256);
|
||||
path_local[255] = '\0';
|
||||
|
||||
|
||||
bool no_dirs = flags & NO_DIRS;
|
||||
bool no_files = flags & NO_FILES;
|
||||
bool hide_ext = flags & HIDE_EXT;
|
||||
bool select_dirs = flags & SELECT_DIRS;
|
||||
|
||||
|
||||
// main loop
|
||||
while (true) {
|
||||
u32 n_found = 0;
|
||||
u32 pos = 0;
|
||||
GetDirContents(contents, path_local);
|
||||
|
||||
|
||||
while (pos < contents->n_entries) {
|
||||
char opt_names[_MAX_FS_OPT+1][32+1];
|
||||
DirEntry* res_entry[MAX_DIR_ENTRIES+1] = { NULL };
|
||||
@ -834,7 +834,7 @@ bool FileSelectorWorker(char* result, const char* text, const char* path, const
|
||||
snprintf(opt_names[n_opt++], 32, "[more...]");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (!new_style) {
|
||||
char temp_str[256];
|
||||
snprintf(temp_str, 256, "%s", entry->name);
|
||||
@ -850,7 +850,7 @@ bool FileSelectorWorker(char* result, const char* text, const char* path, const
|
||||
if ((pos >= contents->n_entries) && (n_opt < n_found) && !new_style)
|
||||
snprintf(opt_names[n_opt++], 32, "[more...]");
|
||||
if (!n_opt) break;
|
||||
|
||||
|
||||
const char* optionstr[_MAX_FS_OPT+1] = { NULL };
|
||||
for (u32 i = 0; i <= _MAX_FS_OPT; i++) optionstr[i] = opt_names[i];
|
||||
u32 user_select = new_style ? ShowFileScrollPrompt(n_opt, (const DirEntry**)res_entry, hide_ext, "%s", text)
|
||||
@ -874,7 +874,7 @@ bool FileSelectorWorker(char* result, const char* text, const char* path, const
|
||||
char pathstr[32+1];
|
||||
TruncateString(pathstr, path_local, 32, 8);
|
||||
ShowPrompt(false, "%s\nNo usable entries found.", pathstr);
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -882,7 +882,7 @@ bool FileSelectorWorker(char* result, const char* text, const char* path, const
|
||||
bool FileSelector(char* result, const char* text, const char* path, const char* pattern, u32 flags, bool new_style) {
|
||||
void* buffer = (void*) malloc(sizeof(DirStruct));
|
||||
if (!buffer) return false;
|
||||
|
||||
|
||||
bool ret = FileSelectorWorker(result, text, path, pattern, flags, buffer, new_style);
|
||||
free(buffer);
|
||||
return ret;
|
||||
|
@ -17,7 +17,7 @@ int ReadImageBytes(void* buffer, u64 offset, u64 count) {
|
||||
if (!mount_state) return FR_INVALID_OBJECT;
|
||||
if (fvx_tell(&mount_file) != offset) {
|
||||
if (fvx_size(&mount_file) < offset) return -1;
|
||||
fvx_lseek(&mount_file, offset);
|
||||
fvx_lseek(&mount_file, offset);
|
||||
}
|
||||
ret = fvx_read(&mount_file, buffer, count, &bytes_read);
|
||||
return (ret != 0) ? (int) ret : (bytes_read != count) ? -1 : 0;
|
||||
|
@ -42,7 +42,7 @@ void dealias_path (TCHAR* alias, const TCHAR* path) {
|
||||
|
||||
FilCryptInfo* fx_find_cryptinfo(FIL* fptr) {
|
||||
FilCryptInfo* info = NULL;
|
||||
|
||||
|
||||
for (u32 i = 0; i < NUM_FILCRYPTINFO; i++) {
|
||||
if (!info && !filcrypt[i].fptr) // use first free
|
||||
info = &filcrypt[i];
|
||||
@ -51,7 +51,7 @@ FilCryptInfo* fx_find_cryptinfo(FIL* fptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@ -59,27 +59,27 @@ FRESULT fx_decrypt_dsiware (FIL* fp, void* buff, FSIZE_t ofs, UINT len) {
|
||||
const u32 mode = AES_CNT_TITLEKEY_DECRYPT_MODE;
|
||||
const u32 num_tbl = sizeof(TadContentTable) / sizeof(u32);
|
||||
const FSIZE_t ofs0 = f_tell(fp);
|
||||
|
||||
|
||||
u8 __attribute__((aligned(16))) iv[AES_BLOCK_SIZE];
|
||||
u32 tbl[num_tbl];
|
||||
u8 hdr[TAD_HEADER_LEN];
|
||||
|
||||
|
||||
FRESULT res;
|
||||
UINT br;
|
||||
|
||||
|
||||
|
||||
|
||||
// read and decrypt header
|
||||
if ((res = f_lseek(fp, TAD_HEADER_OFFSET)) != FR_OK) return res;
|
||||
if ((res = f_read(fp, hdr, TAD_HEADER_LEN, &br)) != FR_OK) return res;
|
||||
if (br != TAD_HEADER_LEN) return FR_DENIED;
|
||||
memcpy(iv, hdr + TAD_HEADER_LEN - AES_BLOCK_SIZE, AES_BLOCK_SIZE);
|
||||
cbc_decrypt(hdr, hdr, sizeof(TadHeader) / AES_BLOCK_SIZE, mode, iv);
|
||||
|
||||
|
||||
// setup the table
|
||||
if (BuildTadContentTable(tbl, hdr) != 0) return FR_DENIED;
|
||||
if (tbl[num_tbl-1] > f_size(fp)) return FR_DENIED; // obviously missing data
|
||||
|
||||
|
||||
|
||||
|
||||
// process sections
|
||||
u32 sct_start = 0;
|
||||
u32 sct_end = 0;
|
||||
@ -87,21 +87,21 @@ FRESULT fx_decrypt_dsiware (FIL* fp, void* buff, FSIZE_t ofs, UINT len) {
|
||||
sct_end = tbl[i];
|
||||
if (sct_start == sct_end) continue; // nothing in section
|
||||
if ((ofs + len <= sct_start) || (ofs >= sct_end)) continue; // section not in data
|
||||
|
||||
|
||||
const u32 crypt_end = sct_end - (AES_BLOCK_SIZE * 2);
|
||||
const u32 data_end = min(crypt_end, ofs + len);
|
||||
u32 data_pos = max(ofs, sct_start);
|
||||
if (ofs >= crypt_end) continue; // nothing to do
|
||||
|
||||
|
||||
if ((sct_start < ofs) || (sct_end > ofs + len)) { // incomplete section, ugh
|
||||
u8 __attribute__((aligned(16))) block[AES_BLOCK_SIZE];
|
||||
|
||||
|
||||
// load iv0
|
||||
FSIZE_t block0_ofs = data_pos - (data_pos % AES_BLOCK_SIZE);
|
||||
FSIZE_t iv0_ofs = ((block0_ofs > sct_start) ? block0_ofs : sct_end) - AES_BLOCK_SIZE;
|
||||
if ((res = f_lseek(fp, iv0_ofs)) != FR_OK) return res;
|
||||
if ((res = f_read(fp, iv, AES_BLOCK_SIZE, &br)) != FR_OK) return res;
|
||||
|
||||
|
||||
// load and decrypt block0 (if misaligned)
|
||||
if (data_pos % AES_BLOCK_SIZE) {
|
||||
if ((res = f_lseek(fp, block0_ofs)) != FR_OK) return res;
|
||||
@ -110,7 +110,7 @@ FRESULT fx_decrypt_dsiware (FIL* fp, void* buff, FSIZE_t ofs, UINT len) {
|
||||
data_pos = min(block0_ofs + AES_BLOCK_SIZE, data_end);
|
||||
memcpy(buff, block + (ofs - block0_ofs), data_pos - ofs);
|
||||
}
|
||||
|
||||
|
||||
// decrypt blocks in between
|
||||
u32 num_blocks = (data_end - data_pos) / AES_BLOCK_SIZE;
|
||||
if (num_blocks) {
|
||||
@ -118,7 +118,7 @@ FRESULT fx_decrypt_dsiware (FIL* fp, void* buff, FSIZE_t ofs, UINT len) {
|
||||
cbc_decrypt(blocks, blocks, num_blocks, mode, iv);
|
||||
data_pos += num_blocks * AES_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
|
||||
// decrypt last block
|
||||
if (data_pos < data_end) {
|
||||
u8* lbuff = (u8*) buff + (data_pos - ofs);
|
||||
@ -137,7 +137,7 @@ FRESULT fx_decrypt_dsiware (FIL* fp, void* buff, FSIZE_t ofs, UINT len) {
|
||||
cbc_decrypt(blocks, blocks, num_blocks, mode, iv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return f_lseek(fp, ofs0);
|
||||
}
|
||||
|
||||
@ -145,7 +145,7 @@ FRESULT fx_open (FIL* fp, const TCHAR* path, BYTE mode) {
|
||||
int num = alias_num(path);
|
||||
FilCryptInfo* info = fx_find_cryptinfo(fp);
|
||||
if (info) info->fptr = NULL;
|
||||
|
||||
|
||||
if (info && (num >= 0)) {
|
||||
// DSIWare Export, mark with the magic number
|
||||
if (strncmp(path + 2, "/" DSIWARE_MAGIC, 1 + 16) == 0) {
|
||||
@ -172,7 +172,7 @@ FRESULT fx_open (FIL* fp, const TCHAR* path, BYTE mode) {
|
||||
memcpy(info->keyy, sd_keyy[num], 16);
|
||||
info->fptr = fp;
|
||||
}
|
||||
|
||||
|
||||
return fa_open(fp, path, mode);
|
||||
}
|
||||
|
||||
@ -193,12 +193,12 @@ FRESULT fx_write (FIL* fp, const void* buff, UINT btw, UINT* bw) {
|
||||
FilCryptInfo* info = fx_find_cryptinfo(fp);
|
||||
FSIZE_t off = f_tell(fp);
|
||||
FRESULT res = FR_OK;
|
||||
|
||||
|
||||
if (info && info->fptr) {
|
||||
if (memcmp(info->ctr, DSIWARE_MAGIC, 16) == 0) return FR_DENIED;
|
||||
void* crypt_buff = (void*) malloc(min(btw, STD_BUFFER_SIZE));
|
||||
if (!crypt_buff) return FR_DENIED;
|
||||
|
||||
|
||||
setup_aeskeyY(0x34, info->keyy);
|
||||
use_aeskey(0x34);
|
||||
*bw = 0;
|
||||
@ -210,7 +210,7 @@ FRESULT fx_write (FIL* fp, const void* buff, UINT btw, UINT* bw) {
|
||||
res = f_write(fp, (const void*) crypt_buff, pcount, &bwl);
|
||||
*bw += bwl;
|
||||
}
|
||||
|
||||
|
||||
free(crypt_buff);
|
||||
} else res = f_write(fp, buff, btw, bw);
|
||||
return res;
|
||||
@ -255,12 +255,12 @@ FRESULT fa_unlink (const TCHAR* path) {
|
||||
// special functions for access of virtual NAND SD drives
|
||||
bool SetupNandSdDrive(const char* path, const char* sd_path, const char* movable, int num) {
|
||||
char alias[128];
|
||||
|
||||
|
||||
// initial checks
|
||||
if ((num >= NUM_ALIAS_DRV) || (num < 0)) return false;
|
||||
alias_drv[num] = 0;
|
||||
if (!sd_path || !movable || !path) return true;
|
||||
|
||||
|
||||
// grab the key Y from movable.sed
|
||||
UINT bytes_read = 0;
|
||||
FIL file;
|
||||
@ -272,13 +272,13 @@ bool SetupNandSdDrive(const char* path, const char* sd_path, const char* movable
|
||||
return false;
|
||||
}
|
||||
f_close(&file);
|
||||
|
||||
|
||||
// build the alias path (id0)
|
||||
u32 sha256sum[8];
|
||||
sha_quick(sha256sum, sd_keyy[num], 0x10, SHA256_MODE);
|
||||
snprintf(alias, 127, "%s/Nintendo 3DS/%08lX%08lX%08lX%08lX",
|
||||
sd_path, sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]);
|
||||
|
||||
|
||||
// find the alias path (id1)
|
||||
char* id1 = alias + strnlen(alias, 127);
|
||||
DIR pdir;
|
||||
@ -297,7 +297,7 @@ bool SetupNandSdDrive(const char* path, const char* sd_path, const char* movable
|
||||
}
|
||||
f_closedir(&pdir);
|
||||
if (!(*id1)) return false;
|
||||
|
||||
|
||||
// create the alias drive
|
||||
return SetupAliasDrive(path, alias, num);
|
||||
}
|
||||
@ -307,12 +307,12 @@ bool SetupAliasDrive(const char* path, const char* alias, int num) {
|
||||
if ((num >= NUM_ALIAS_DRV) || (num < 0)) return false;
|
||||
alias_drv[num] = 0;
|
||||
if (!alias || !path) return true;
|
||||
|
||||
|
||||
// take over drive path and alias
|
||||
strncpy(alias_path[num], alias, 128);
|
||||
if (path[1] != ':') return false;
|
||||
alias_drv[num] = path[0];
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ bool CheckSupportFile(const char* fname)
|
||||
// try VRAM0 first
|
||||
if (FindVTarFileInfo(fname, NULL))
|
||||
return true;
|
||||
|
||||
|
||||
// try support file paths
|
||||
const char* base_paths[] = { SUPPORT_FILE_PATHS };
|
||||
for (u32 i = 0; i < countof(base_paths); i++) {
|
||||
@ -21,7 +21,7 @@ bool CheckSupportFile(const char* fname)
|
||||
if (fvx_stat(path, NULL) == FR_OK)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ size_t LoadSupportFile(const char* fname, void* buffer, size_t max_len)
|
||||
memcpy(buffer, data, len64);
|
||||
return (size_t) len64;
|
||||
}
|
||||
|
||||
|
||||
// try support file paths
|
||||
const char* base_paths[] = { SUPPORT_FILE_PATHS };
|
||||
for (u32 i = 0; i < countof(base_paths); i++) {
|
||||
@ -44,7 +44,7 @@ size_t LoadSupportFile(const char* fname, void* buffer, size_t max_len)
|
||||
if (fvx_qread(path, buffer, 0, max_len, &len32) == FR_OK)
|
||||
return len32;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ bool SaveSupportFile(const char* fname, void* buffer, size_t len)
|
||||
if (fvx_qwrite(path, buffer, 0, len, NULL) == FR_OK)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ bool GetSupportDir(char* path, const char* dname)
|
||||
if ((fvx_stat(path, &fno) == FR_OK) && (fno.fattrib & AM_DIR))
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -151,22 +151,22 @@ FRESULT fvx_qread (const TCHAR* path, void* buff, FSIZE_t ofs, UINT btr, UINT* b
|
||||
FIL fp;
|
||||
FRESULT res;
|
||||
UINT brt = 0;
|
||||
|
||||
|
||||
res = fvx_open(&fp, path, FA_READ | FA_OPEN_EXISTING);
|
||||
if (res != FR_OK) return res;
|
||||
|
||||
|
||||
res = fvx_lseek(&fp, ofs);
|
||||
if (res != FR_OK) {
|
||||
fvx_close(&fp);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
res = fvx_read(&fp, buff, btr, &brt);
|
||||
fvx_close(&fp);
|
||||
|
||||
|
||||
if (br) *br = brt;
|
||||
else if ((res == FR_OK) && (brt != btr)) res = FR_DENIED;
|
||||
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -174,29 +174,29 @@ FRESULT fvx_qwrite (const TCHAR* path, const void* buff, FSIZE_t ofs, UINT btw,
|
||||
FIL fp;
|
||||
FRESULT res;
|
||||
UINT bwt = 0;
|
||||
|
||||
|
||||
res = fvx_open(&fp, path, FA_WRITE | FA_OPEN_ALWAYS);
|
||||
if (res != FR_OK) return res;
|
||||
|
||||
|
||||
res = fvx_lseek(&fp, ofs);
|
||||
if (res != FR_OK) {
|
||||
fvx_close(&fp);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
res = fvx_write(&fp, buff, btw, &bwt);
|
||||
fvx_close(&fp);
|
||||
|
||||
|
||||
if (bw) *bw = bwt;
|
||||
else if ((res == FR_OK) && (bwt != btw)) res = FR_DENIED;
|
||||
|
||||
|
||||
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;
|
||||
|
||||
@ -212,7 +212,7 @@ FRESULT fvx_qfill (const TCHAR* path, const void* buff, UINT btb) {
|
||||
FRESULT res;
|
||||
UINT bwtt = 0;
|
||||
UINT fsiz = 0;
|
||||
|
||||
|
||||
res = fvx_open(&fp, path, FA_WRITE | FA_OPEN_EXISTING);
|
||||
if (res != FR_OK) return res;
|
||||
|
||||
@ -281,17 +281,17 @@ FRESULT fvx_rmkpath (const TCHAR* path) {
|
||||
FRESULT worker_fvx_runlink (TCHAR* tpath) {
|
||||
FILINFO fno;
|
||||
FRESULT res;
|
||||
|
||||
|
||||
// this code handles directory content deletion
|
||||
if ((res = fvx_stat(tpath, &fno)) != FR_OK) return res; // tpath does not exist
|
||||
if (fno.fattrib & AM_DIR) { // process folder contents
|
||||
DIR pdir;
|
||||
TCHAR* fname = tpath + strnlen(tpath, 255);
|
||||
|
||||
|
||||
|
||||
|
||||
if ((res = fa_opendir(&pdir, tpath)) != FR_OK) return res;
|
||||
*(fname++) = '/';
|
||||
|
||||
|
||||
while (fvx_readdir(&pdir, &fno) == FR_OK) {
|
||||
if ((strncmp(fno.fname, ".", 2) == 0) || (strncmp(fno.fname, "..", 3) == 0))
|
||||
continue; // filter out virtual entries
|
||||
@ -305,7 +305,7 @@ FRESULT worker_fvx_runlink (TCHAR* tpath) {
|
||||
fvx_closedir(&pdir);
|
||||
*(--fname) = '\0';
|
||||
}
|
||||
|
||||
|
||||
return fvx_unlink( tpath );
|
||||
}
|
||||
#endif
|
||||
@ -344,7 +344,7 @@ FRESULT fvx_match_name(const TCHAR* path, const TCHAR* pattern) {
|
||||
if (fvx_match_name(path, pattern + 1) == FR_OK) return FR_OK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return FR_NO_FILE;
|
||||
}
|
||||
|
||||
@ -360,19 +360,19 @@ FRESULT fvx_findpath (TCHAR* path, const TCHAR* pattern, BYTE mode) {
|
||||
TCHAR* fname = strrchr(path, '/');
|
||||
if (!fname) return FR_DENIED;
|
||||
*fname = '\0';
|
||||
|
||||
|
||||
TCHAR* npattern = strrchr(pattern, '/');
|
||||
if (!npattern) return FR_DENIED;
|
||||
npattern++;
|
||||
|
||||
|
||||
DIR pdir;
|
||||
FILINFO fno;
|
||||
FRESULT res;
|
||||
if ((res = fvx_opendir(&pdir, path)) != FR_OK) return res;
|
||||
|
||||
|
||||
*(fname++) = '/';
|
||||
*fname = '\0';
|
||||
|
||||
|
||||
while ((fvx_preaddir(&pdir, &fno, npattern) == FR_OK) && *(fno.fname)) {
|
||||
int cmp = strncmp(fno.fname, fname, _MAX_FN_LEN);
|
||||
if (((mode & FN_HIGHEST) && (cmp > 0)) || ((mode & FN_LOWEST) && (cmp < 0)) || !(*fname))
|
||||
@ -380,7 +380,7 @@ FRESULT fvx_findpath (TCHAR* path, const TCHAR* pattern, BYTE mode) {
|
||||
if (!(mode & (FN_HIGHEST|FN_LOWEST))) break;
|
||||
}
|
||||
fvx_closedir( &pdir );
|
||||
|
||||
|
||||
return (*fname) ? FR_OK : FR_NO_PATH;
|
||||
}
|
||||
|
||||
@ -389,7 +389,7 @@ FRESULT fvx_findnopath (TCHAR* path, const TCHAR* pattern) {
|
||||
TCHAR* fname = strrchr(path, '/');
|
||||
if (!fname) return FR_DENIED;
|
||||
fname++;
|
||||
|
||||
|
||||
TCHAR* rep[16];
|
||||
u32 n_rep = 0;
|
||||
for (u32 i = 0; fname[i]; i++) {
|
||||
@ -400,7 +400,7 @@ FRESULT fvx_findnopath (TCHAR* path, const TCHAR* pattern) {
|
||||
if (n_rep >= 16) return FR_DENIED;
|
||||
}
|
||||
if (!n_rep) return (fvx_stat(path, NULL) == FR_OK) ? FR_NO_PATH : FR_OK;
|
||||
|
||||
|
||||
while (fvx_stat(path, NULL) == FR_OK) {
|
||||
for (int i = n_rep - 1; (i >= 0); i--) {
|
||||
if (*(rep[i]) == '9') {
|
||||
@ -412,7 +412,7 @@ FRESULT fvx_findnopath (TCHAR* path, const TCHAR* pattern) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return FR_OK;
|
||||
}
|
||||
|
||||
|
@ -105,8 +105,8 @@ static FRESULT BDRIWrite(UINT ofs, UINT btw, const void* buf) {
|
||||
bool CheckDBMagic(const u8* pre_header, bool tickdb) {
|
||||
const TitleDBPreHeader* title = (TitleDBPreHeader*) pre_header;
|
||||
const TickDBPreHeader* tick = (TickDBPreHeader*) pre_header;
|
||||
|
||||
return (tickdb ? ((strncmp(tick->magic, "TICK", 4) == 0) && (tick->unknown1 == 1)) :
|
||||
|
||||
return (tickdb ? ((strncmp(tick->magic, "TICK", 4) == 0) && (tick->unknown1 == 1)) :
|
||||
((strcmp(title->magic, "NANDIDB") == 0) || (strcmp(title->magic, "NANDTDB") == 0) ||
|
||||
(strcmp(title->magic, "TEMPIDB") == 0) || (strcmp(title->magic, "TEMPTDB") == 0))) &&
|
||||
(strcmp((tickdb ? tick->fs_header : title->fs_header).magic, "BDRI") == 0) &&
|
||||
@ -133,13 +133,13 @@ static u32 GetBDRIEntrySize(const BDRIFsHeader* fs_header, const u32 fs_header_o
|
||||
const u32 data_offset = fs_header_offset + fs_header->data_offset;
|
||||
const u32 fet_offset = data_offset + fs_header->fet_start_block * fs_header->data_block_size;
|
||||
const u32 fht_offset = fs_header_offset + fs_header->fht_offset;
|
||||
|
||||
|
||||
u32 index = 0;
|
||||
TdbFileEntry file_entry;
|
||||
u64 tid_be = getbe64(title_id);
|
||||
u8* title_id_be = (u8*) &tid_be;
|
||||
const u32 hash_bucket = GetHashBucket(title_id_be, 1, fs_header->fht_bucket_count);
|
||||
|
||||
|
||||
if (BDRIRead(fht_offset + hash_bucket * sizeof(u32), sizeof(u32), &(file_entry.hash_bucket_next_index)) != FR_OK)
|
||||
return 1;
|
||||
|
||||
@ -147,13 +147,13 @@ static u32 GetBDRIEntrySize(const BDRIFsHeader* fs_header, const u32 fs_header_o
|
||||
do {
|
||||
if (file_entry.hash_bucket_next_index == 0)
|
||||
return 1;
|
||||
|
||||
|
||||
index = file_entry.hash_bucket_next_index;
|
||||
|
||||
|
||||
if (BDRIRead(fet_offset + index * sizeof(TdbFileEntry), sizeof(TdbFileEntry), &file_entry) != FR_OK)
|
||||
return 1;
|
||||
} while (memcmp(title_id_be, file_entry.title_id, 8) != 0);
|
||||
|
||||
|
||||
*size = file_entry.size;
|
||||
|
||||
return 0;
|
||||
@ -162,18 +162,18 @@ static u32 GetBDRIEntrySize(const BDRIFsHeader* fs_header, const u32 fs_header_o
|
||||
static u32 ReadBDRIEntry(const BDRIFsHeader* fs_header, const u32 fs_header_offset, const u8* title_id, u8* entry, const u32 expected_size) {
|
||||
if ((fs_header->info_offset != 0x20) || (fs_header->fat_entry_count != fs_header->data_block_count)) // Could be more thorough
|
||||
return 1;
|
||||
|
||||
|
||||
const u32 data_offset = fs_header_offset + fs_header->data_offset;
|
||||
const u32 fet_offset = data_offset + fs_header->fet_start_block * fs_header->data_block_size;
|
||||
const u32 fht_offset = fs_header_offset + fs_header->fht_offset;
|
||||
const u32 fat_offset = fs_header_offset + fs_header->fat_offset;
|
||||
|
||||
|
||||
u32 index = 0;
|
||||
TdbFileEntry file_entry;
|
||||
u64 tid_be = getbe64(title_id);
|
||||
u8* title_id_be = (u8*) &tid_be;
|
||||
const u32 hash_bucket = GetHashBucket(title_id_be, 1, fs_header->fht_bucket_count);
|
||||
|
||||
|
||||
if (BDRIRead(fht_offset + hash_bucket * sizeof(u32), sizeof(u32), &(file_entry.hash_bucket_next_index)) != FR_OK)
|
||||
return 1;
|
||||
|
||||
@ -181,108 +181,108 @@ static u32 ReadBDRIEntry(const BDRIFsHeader* fs_header, const u32 fs_header_offs
|
||||
do {
|
||||
if (file_entry.hash_bucket_next_index == 0)
|
||||
return 1;
|
||||
|
||||
|
||||
index = file_entry.hash_bucket_next_index;
|
||||
|
||||
|
||||
if (BDRIRead(fet_offset + index * sizeof(TdbFileEntry), sizeof(TdbFileEntry), &file_entry) != FR_OK)
|
||||
return 1;
|
||||
} while (memcmp(title_id_be, file_entry.title_id, 8) != 0);
|
||||
|
||||
|
||||
if (expected_size && (file_entry.size != expected_size))
|
||||
return 1;
|
||||
|
||||
|
||||
index = file_entry.start_block_index + 1; // FAT entry index
|
||||
|
||||
|
||||
u32 bytes_read = 0;
|
||||
u32 fat_entry[2];
|
||||
|
||||
|
||||
while (bytes_read < file_entry.size) { // Read the full entry, walking the FAT node chain
|
||||
u32 read_start = index - 1; // Data region block index
|
||||
u32 read_count = 0;
|
||||
|
||||
|
||||
if (BDRIRead(fat_offset + index * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
if ((bytes_read == 0) && !getfatflag(fat_entry[0]))
|
||||
return 1;
|
||||
|
||||
|
||||
u32 next_index = getfatindex(fat_entry[1]);
|
||||
|
||||
|
||||
if (getfatflag(fat_entry[1])) { // Multi-entry node
|
||||
if (BDRIRead(fat_offset + (index + 1) * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
if (!getfatflag(fat_entry[0]) || getfatflag(fat_entry[1]) || (getfatindex(fat_entry[0]) != index) || (getfatindex(fat_entry[0]) >= getfatindex(fat_entry[1])))
|
||||
return 1;
|
||||
|
||||
|
||||
read_count = getfatindex(fat_entry[1]) + 1 - index;
|
||||
} else { // Single-entry node
|
||||
read_count = 1;
|
||||
}
|
||||
|
||||
|
||||
index = next_index;
|
||||
|
||||
|
||||
u32 btr = min(file_entry.size - bytes_read, read_count * fs_header->data_block_size);
|
||||
if (entry && (BDRIRead(data_offset + read_start * fs_header->data_block_size, btr, entry + bytes_read) != FR_OK))
|
||||
return 1;
|
||||
|
||||
|
||||
bytes_read += btr;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 RemoveBDRIEntry(const BDRIFsHeader* fs_header, const u32 fs_header_offset, const u8* title_id) {
|
||||
if ((fs_header->info_offset != 0x20) || (fs_header->fat_entry_count != fs_header->data_block_count)) // Could be more thorough
|
||||
return 1;
|
||||
|
||||
|
||||
const u32 data_offset = fs_header_offset + fs_header->data_offset;
|
||||
const u32 det_offset = data_offset + fs_header->det_start_block * fs_header->data_block_size;
|
||||
const u32 fet_offset = data_offset + fs_header->fet_start_block * fs_header->data_block_size;
|
||||
const u32 fht_offset = fs_header_offset + fs_header->fht_offset;
|
||||
const u32 fat_offset = fs_header_offset + fs_header->fat_offset;
|
||||
|
||||
|
||||
u32 index = 0, previous_index = 0;
|
||||
TdbFileEntry file_entry;
|
||||
u64 tid_be = getbe64(title_id);
|
||||
u8* title_id_be = (u8*) &tid_be;
|
||||
|
||||
|
||||
// Read the index of the first file entry from the directory entry table
|
||||
if (BDRIRead(det_offset + 0x2C, sizeof(u32), &(file_entry.next_sibling_index)) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
// Find the file entry for the tid specified, fail if it doesn't exist
|
||||
do {
|
||||
previous_index = index;
|
||||
index = file_entry.next_sibling_index;
|
||||
|
||||
|
||||
if (index == 0)
|
||||
return 1;
|
||||
|
||||
|
||||
if (BDRIRead(fet_offset + index * sizeof(TdbFileEntry), sizeof(TdbFileEntry), &file_entry) != FR_OK)
|
||||
return 1;
|
||||
} while (memcmp(title_id_be, file_entry.title_id, 8) != 0);
|
||||
|
||||
|
||||
DummyFileEntry dummy_entry;
|
||||
|
||||
|
||||
// Read the 0th entry in the FET, which is always a dummy entry
|
||||
if (BDRIRead(fet_offset, sizeof(DummyFileEntry), &dummy_entry) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
if (dummy_entry.max_entry_count != fs_header->max_file_count + 1)
|
||||
return 1;
|
||||
|
||||
|
||||
if ((BDRIWrite(fet_offset + index * sizeof(TdbFileEntry), sizeof(TdbFileEntry), &dummy_entry) != FR_OK) ||
|
||||
(BDRIWrite(fet_offset + 0x28, sizeof(u32), &index) != FR_OK) ||
|
||||
(BDRIWrite((previous_index == 0) ? det_offset + 0x2C : fet_offset + previous_index * sizeof(TdbFileEntry) + 0xC, sizeof(u32), &(file_entry.next_sibling_index)) != FR_OK))
|
||||
return 1;
|
||||
|
||||
|
||||
const u32 hash_bucket = GetHashBucket(file_entry.title_id, file_entry.parent_index, fs_header->fht_bucket_count);
|
||||
u32 index_hash = 0;
|
||||
|
||||
|
||||
if (BDRIRead(fht_offset + hash_bucket * sizeof(u32), sizeof(u32), &index_hash) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
if (index_hash == index) {
|
||||
if (BDRIWrite(fht_offset + hash_bucket * sizeof(u32), sizeof(u32), &(file_entry.hash_bucket_next_index)) != FR_OK)
|
||||
return 1;
|
||||
@ -290,83 +290,83 @@ static u32 RemoveBDRIEntry(const BDRIFsHeader* fs_header, const u32 fs_header_of
|
||||
do {
|
||||
if (index_hash == 0) // This shouldn't happen if the entry was properly added
|
||||
break;
|
||||
|
||||
|
||||
if (BDRIRead(fet_offset + index_hash * sizeof(TdbFileEntry) + 0x28, sizeof(u32), &index_hash) != FR_OK)
|
||||
return 1;
|
||||
} while (index_hash != index);
|
||||
|
||||
|
||||
if ((index_hash != 0) && BDRIWrite(fet_offset + index_hash * sizeof(TdbFileEntry) + 0x28, sizeof(u32), &(file_entry.hash_bucket_next_index)) != FR_OK)
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
u32 fat_entry[2];
|
||||
|
||||
|
||||
if (BDRIRead(fat_offset, FAT_ENTRY_SIZE, fat_entry) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
if (getfatflag(fat_entry[1]) || (fat_entry[0] != 0))
|
||||
return 1;
|
||||
|
||||
|
||||
u32 next_free_index = getfatindex(fat_entry[1]), fat_index = file_entry.start_block_index + 1;
|
||||
|
||||
|
||||
if (BDRIWrite(fat_offset + sizeof(u32), sizeof(u32), &fat_index) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
fat_entry[1] = fat_index;
|
||||
|
||||
|
||||
do {
|
||||
fat_index = getfatindex(fat_entry[1]);
|
||||
|
||||
|
||||
if (BDRIRead(fat_offset + FAT_ENTRY_SIZE * fat_index, FAT_ENTRY_SIZE, fat_entry) != FR_OK)
|
||||
return 1;
|
||||
} while (getfatindex(fat_entry[1]) != 0);
|
||||
|
||||
|
||||
fat_entry[1] |= next_free_index;
|
||||
|
||||
|
||||
if ((BDRIWrite(fat_offset + fat_index * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK) ||
|
||||
(BDRIRead(fat_offset + next_free_index * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK))
|
||||
return 1;
|
||||
|
||||
|
||||
fat_entry[0] = buildfatuv(fat_index, false);
|
||||
|
||||
|
||||
if (BDRIWrite(fat_offset + next_free_index * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 AddBDRIEntry(const BDRIFsHeader* fs_header, const u32 fs_header_offset, const u8* title_id, const u8* entry, const u32 size, bool replace) {
|
||||
if ((fs_header->info_offset != 0x20) || (fs_header->fat_entry_count != fs_header->data_block_count)) // Could be more thorough
|
||||
return 1;
|
||||
|
||||
|
||||
if (!entry || !size)
|
||||
return 1;
|
||||
|
||||
|
||||
const u32 data_offset = fs_header_offset + fs_header->data_offset;
|
||||
const u32 det_offset = data_offset + fs_header->det_start_block * fs_header->data_block_size;
|
||||
const u32 fet_offset = data_offset + fs_header->fet_start_block * fs_header->data_block_size;
|
||||
const u32 fht_offset = fs_header_offset + fs_header->fht_offset;
|
||||
const u32 fat_offset = fs_header_offset + fs_header->fat_offset;
|
||||
const u32 size_blocks = (size / fs_header->data_block_size) + (((size % fs_header->data_block_size) == 0) ? 0 : 1);
|
||||
|
||||
|
||||
u32 index = 0, max_index = 0;
|
||||
TdbFileEntry file_entry;
|
||||
u64 tid_be = getbe64(title_id);
|
||||
u8* title_id_be = (u8*) &tid_be;
|
||||
bool do_replace = false;
|
||||
|
||||
|
||||
// Read the index of the first file entry from the directory entry table
|
||||
if (BDRIRead(det_offset + 0x2C, sizeof(u32), &(file_entry.next_sibling_index)) != FR_OK)
|
||||
if (BDRIRead(det_offset + 0x2C, sizeof(u32), &(file_entry.next_sibling_index)) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
// Try to find the file entry for the tid specified
|
||||
while (file_entry.next_sibling_index != 0) {
|
||||
index = file_entry.next_sibling_index;
|
||||
max_index = max(index, max_index);
|
||||
|
||||
max_index = max(index, max_index);
|
||||
|
||||
if (BDRIRead(fet_offset + index * sizeof(TdbFileEntry), sizeof(TdbFileEntry), &file_entry) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
// If an entry for the tid already existed that is already the specified size and replace was specified, just replace the existing entry
|
||||
if (memcmp(title_id_be, file_entry.title_id, 8) == 0) {
|
||||
if (!replace || (file_entry.size != size)) return 1;
|
||||
@ -376,10 +376,10 @@ static u32 AddBDRIEntry(const BDRIFsHeader* fs_header, const u32 fs_header_offse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
u32 fat_entry[2];
|
||||
u32 fat_index = 0;
|
||||
|
||||
|
||||
if (!do_replace) {
|
||||
if (BDRIRead(fat_offset, FAT_ENTRY_SIZE, fat_entry) != FR_OK)
|
||||
return 1;
|
||||
@ -476,48 +476,48 @@ static u32 AddBDRIEntry(const BDRIFsHeader* fs_header, const u32 fs_header_offse
|
||||
if (BDRIWrite(fat_offset + previous_free_index * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK)
|
||||
return 1;
|
||||
} else fat_index = file_entry.start_block_index + 1;
|
||||
|
||||
|
||||
u32 bytes_written = 0, fat_index_write = fat_index;
|
||||
|
||||
|
||||
while (bytes_written < size) { // Write the full entry, walking the FAT node chain
|
||||
// Can't assume contiguity here, because we might be replacing an existing entry
|
||||
u32 write_start = fat_index_write - 1; // Data region block index
|
||||
u32 write_count = 0;
|
||||
|
||||
|
||||
if (BDRIRead(fat_offset + fat_index_write * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
if ((bytes_written == 0) && !getfatflag(fat_entry[0]))
|
||||
return 1;
|
||||
|
||||
|
||||
u32 next_index = getfatindex(fat_entry[1]);
|
||||
|
||||
|
||||
if (getfatflag(fat_entry[1])) { // Multi-entry node
|
||||
if (BDRIRead(fat_offset + (fat_index_write + 1) * FAT_ENTRY_SIZE, FAT_ENTRY_SIZE, fat_entry) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
if (!getfatflag(fat_entry[0]) || getfatflag(fat_entry[1]) || (getfatindex(fat_entry[0]) != fat_index_write) || (getfatindex(fat_entry[0]) >= getfatindex(fat_entry[1])))
|
||||
return 1;
|
||||
|
||||
|
||||
write_count = getfatindex(fat_entry[1]) + 1 - fat_index_write;
|
||||
} else { // Single-entry node
|
||||
write_count = 1;
|
||||
}
|
||||
|
||||
|
||||
fat_index_write = next_index;
|
||||
|
||||
|
||||
u32 btw = min(size - bytes_written, write_count * fs_header->data_block_size);
|
||||
if (BDRIWrite(data_offset + write_start * fs_header->data_block_size, btw, entry + bytes_written) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
bytes_written += btw;
|
||||
}
|
||||
|
||||
|
||||
if (!do_replace) {
|
||||
DummyFileEntry dummy_entry;
|
||||
|
||||
// Read the 0th entry in the FET, which is always a dummy entry
|
||||
if (BDRIRead(fet_offset, sizeof(DummyFileEntry), &dummy_entry) != FR_OK)
|
||||
if (BDRIRead(fet_offset, sizeof(DummyFileEntry), &dummy_entry) != FR_OK)
|
||||
return 1;
|
||||
|
||||
if (dummy_entry.max_entry_count != fs_header->max_file_count + 1)
|
||||
@ -559,83 +559,83 @@ static u32 AddBDRIEntry(const BDRIFsHeader* fs_header, const u32 fs_header_offse
|
||||
if (BDRIWrite(fet_offset + index * sizeof(TdbFileEntry), sizeof(TdbFileEntry), &file_entry) != FR_OK)
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 GetNumBDRIEntries(const BDRIFsHeader* fs_header, const u32 fs_header_offset) {
|
||||
if ((fs_header->info_offset != 0x20) || (fs_header->fat_entry_count != fs_header->data_block_count)) // Could be more thorough
|
||||
return 0;
|
||||
|
||||
return 0;
|
||||
|
||||
const u32 data_offset = fs_header_offset + fs_header->data_offset;
|
||||
const u32 det_offset = data_offset + fs_header->det_start_block * fs_header->data_block_size;
|
||||
const u32 fet_offset = data_offset + fs_header->fet_start_block * fs_header->data_block_size;
|
||||
|
||||
|
||||
u32 num_entries = 0;
|
||||
TdbFileEntry file_entry;
|
||||
|
||||
|
||||
// Read the index of the first file entry from the directory entry table
|
||||
if (BDRIRead(det_offset + 0x2C, sizeof(u32), &(file_entry.next_sibling_index)) != FR_OK)
|
||||
return 0;
|
||||
|
||||
|
||||
while (file_entry.next_sibling_index != 0) {
|
||||
num_entries++;
|
||||
if (BDRIRead(fet_offset + file_entry.next_sibling_index * sizeof(TdbFileEntry), sizeof(TdbFileEntry), &file_entry) != FR_OK)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
return num_entries;
|
||||
}
|
||||
|
||||
static u32 ListBDRIEntryTitleIDs(const BDRIFsHeader* fs_header, const u32 fs_header_offset, u8* title_ids, u32 max_title_ids) {
|
||||
if ((fs_header->info_offset != 0x20) || (fs_header->fat_entry_count != fs_header->data_block_count))
|
||||
return 0;
|
||||
|
||||
|
||||
const u32 data_offset = fs_header_offset + fs_header->data_offset;
|
||||
const u32 det_offset = data_offset + fs_header->det_start_block * fs_header->data_block_size;
|
||||
const u32 fet_offset = data_offset + fs_header->fet_start_block * fs_header->data_block_size;
|
||||
|
||||
|
||||
u32 num_entries = 0;
|
||||
TdbFileEntry file_entry;
|
||||
|
||||
|
||||
for (u32 i = 0; i < max_title_ids; i++)
|
||||
memset(title_ids, 0, max_title_ids * 8);
|
||||
|
||||
|
||||
// Read the index of the first file entry from the directory entry table
|
||||
if (BDRIRead(det_offset + 0x2C, sizeof(u32), &(file_entry.next_sibling_index)) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
while ((file_entry.next_sibling_index != 0) && (num_entries < max_title_ids)) {
|
||||
if (BDRIRead(fet_offset + file_entry.next_sibling_index * sizeof(TdbFileEntry), sizeof(TdbFileEntry), &file_entry) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
u64 tid_be = getbe64(file_entry.title_id);
|
||||
memcpy(title_ids + num_entries * 8, (u8*) &tid_be, 8);
|
||||
|
||||
|
||||
num_entries++;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 GetNumTitleInfoEntries(const char* path) {
|
||||
FIL file;
|
||||
TitleDBPreHeader pre_header;
|
||||
|
||||
|
||||
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 0;
|
||||
|
||||
|
||||
bdrifp = &file;
|
||||
|
||||
|
||||
if ((BDRIRead(0, sizeof(TitleDBPreHeader), &pre_header) != FR_OK) ||
|
||||
!CheckDBMagic((u8*) &pre_header, false)) {
|
||||
fvx_close(bdrifp);
|
||||
bdrifp = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
u32 num = GetNumBDRIEntries(&(pre_header.fs_header), sizeof(TitleDBPreHeader) - sizeof(BDRIFsHeader));
|
||||
|
||||
|
||||
fvx_close(bdrifp);
|
||||
bdrifp = NULL;
|
||||
return num;
|
||||
@ -644,12 +644,12 @@ u32 GetNumTitleInfoEntries(const char* path) {
|
||||
u32 GetNumTickets(const char* path) {
|
||||
FIL file;
|
||||
TickDBPreHeader pre_header;
|
||||
|
||||
|
||||
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 0;
|
||||
|
||||
|
||||
bdrifp = &file;
|
||||
|
||||
|
||||
if ((BDRIRead(0, sizeof(TickDBPreHeader), &pre_header) != FR_OK) ||
|
||||
!CheckDBMagic((u8*) &pre_header, true)) {
|
||||
fvx_close(bdrifp);
|
||||
@ -658,7 +658,7 @@ u32 GetNumTickets(const char* path) {
|
||||
}
|
||||
|
||||
u32 num = GetNumBDRIEntries(&(pre_header.fs_header), sizeof(TickDBPreHeader) - sizeof(BDRIFsHeader));
|
||||
|
||||
|
||||
fvx_close(bdrifp);
|
||||
bdrifp = NULL;
|
||||
return num;
|
||||
@ -667,12 +667,12 @@ u32 GetNumTickets(const char* path) {
|
||||
u32 ListTitleInfoEntryTitleIDs(const char* path, u8* title_ids, u32 max_title_ids) {
|
||||
FIL file;
|
||||
TitleDBPreHeader pre_header;
|
||||
|
||||
|
||||
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
bdrifp = &file;
|
||||
|
||||
|
||||
if ((BDRIRead(0, sizeof(TitleDBPreHeader), &pre_header) != FR_OK) ||
|
||||
!CheckDBMagic((u8*) &pre_header, false) ||
|
||||
(ListBDRIEntryTitleIDs(&(pre_header.fs_header), sizeof(TitleDBPreHeader) - sizeof(BDRIFsHeader), title_ids, max_title_ids) != 0)) {
|
||||
@ -680,7 +680,7 @@ u32 ListTitleInfoEntryTitleIDs(const char* path, u8* title_ids, u32 max_title_id
|
||||
bdrifp = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
fvx_close(bdrifp);
|
||||
bdrifp = NULL;
|
||||
return 0;
|
||||
@ -689,12 +689,12 @@ u32 ListTitleInfoEntryTitleIDs(const char* path, u8* title_ids, u32 max_title_id
|
||||
u32 ListTicketTitleIDs(const char* path, u8* title_ids, u32 max_title_ids) {
|
||||
FIL file;
|
||||
TickDBPreHeader pre_header;
|
||||
|
||||
|
||||
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
bdrifp = &file;
|
||||
|
||||
|
||||
if ((BDRIRead(0, sizeof(TickDBPreHeader), &pre_header) != FR_OK) ||
|
||||
!CheckDBMagic((u8*) &pre_header, true) ||
|
||||
(ListBDRIEntryTitleIDs(&(pre_header.fs_header), sizeof(TickDBPreHeader) - sizeof(BDRIFsHeader), title_ids, max_title_ids) != 0)) {
|
||||
@ -702,7 +702,7 @@ u32 ListTicketTitleIDs(const char* path, u8* title_ids, u32 max_title_ids) {
|
||||
bdrifp = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
fvx_close(bdrifp);
|
||||
bdrifp = NULL;
|
||||
return 0;
|
||||
@ -711,12 +711,12 @@ u32 ListTicketTitleIDs(const char* path, u8* title_ids, u32 max_title_ids) {
|
||||
u32 ReadTitleInfoEntryFromDB(const char* path, const u8* title_id, TitleInfoEntry* tie) {
|
||||
FIL file;
|
||||
TitleDBPreHeader pre_header;
|
||||
|
||||
|
||||
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
bdrifp = &file;
|
||||
|
||||
|
||||
if ((BDRIRead(0, sizeof(TitleDBPreHeader), &pre_header) != FR_OK) ||
|
||||
!CheckDBMagic((u8*) &pre_header, false) ||
|
||||
(ReadBDRIEntry(&(pre_header.fs_header), sizeof(TitleDBPreHeader) - sizeof(BDRIFsHeader), title_id, (u8*) tie,
|
||||
@ -725,7 +725,7 @@ u32 ReadTitleInfoEntryFromDB(const char* path, const u8* title_id, TitleInfoEntr
|
||||
bdrifp = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
fvx_close(bdrifp);
|
||||
bdrifp = NULL;
|
||||
return 0;
|
||||
@ -739,13 +739,13 @@ u32 ReadTicketFromDB(const char* path, const u8* title_id, Ticket** ticket) {
|
||||
|
||||
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
bdrifp = &file;
|
||||
|
||||
|
||||
if ((BDRIRead(0, sizeof(TickDBPreHeader), &pre_header) != FR_OK) ||
|
||||
!CheckDBMagic((u8*) &pre_header, true) ||
|
||||
(GetBDRIEntrySize(&(pre_header.fs_header), sizeof(TickDBPreHeader) - sizeof(BDRIFsHeader), title_id, &entry_size) != 0) ||
|
||||
entry_size < sizeof(TicketEntry) + 0x14 ||
|
||||
entry_size < sizeof(TicketEntry) + 0x14 ||
|
||||
(te = (TicketEntry*)malloc(entry_size), te == NULL) ||
|
||||
(ReadBDRIEntry(&(pre_header.fs_header), sizeof(TickDBPreHeader) - sizeof(BDRIFsHeader), title_id, (u8*) te,
|
||||
entry_size) != 0)) {
|
||||
@ -754,15 +754,15 @@ u32 ReadTicketFromDB(const char* path, const u8* title_id, Ticket** ticket) {
|
||||
bdrifp = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
fvx_close(bdrifp);
|
||||
bdrifp = NULL;
|
||||
|
||||
|
||||
if (te->ticket_size != GetTicketSize(&te->ticket)) {
|
||||
free(te);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
if (ticket) {
|
||||
u32 size = te->ticket_size;
|
||||
memmove(te, &te->ticket, size); // recycle this memory, instead of allocating another
|
||||
@ -771,7 +771,7 @@ u32 ReadTicketFromDB(const char* path, const u8* title_id, Ticket** ticket) {
|
||||
*ticket = tik;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
free(te);
|
||||
return 0;
|
||||
}
|
||||
@ -779,12 +779,12 @@ u32 ReadTicketFromDB(const char* path, const u8* title_id, Ticket** ticket) {
|
||||
u32 RemoveTitleInfoEntryFromDB(const char* path, const u8* title_id) {
|
||||
FIL file;
|
||||
TitleDBPreHeader pre_header;
|
||||
|
||||
|
||||
if (fvx_open(&file, path, FA_READ | FA_WRITE | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
bdrifp = &file;
|
||||
|
||||
|
||||
if ((BDRIRead(0, sizeof(TitleDBPreHeader), &pre_header) != FR_OK) ||
|
||||
!CheckDBMagic((u8*) &pre_header, false) ||
|
||||
(RemoveBDRIEntry(&(pre_header.fs_header), sizeof(TitleDBPreHeader) - sizeof(BDRIFsHeader), title_id) != 0)) {
|
||||
@ -792,7 +792,7 @@ u32 RemoveTitleInfoEntryFromDB(const char* path, const u8* title_id) {
|
||||
bdrifp = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
fvx_close(bdrifp);
|
||||
bdrifp = NULL;
|
||||
return 0;
|
||||
@ -801,12 +801,12 @@ u32 RemoveTitleInfoEntryFromDB(const char* path, const u8* title_id) {
|
||||
u32 RemoveTicketFromDB(const char* path, const u8* title_id) {
|
||||
FIL file;
|
||||
TickDBPreHeader pre_header;
|
||||
|
||||
|
||||
if (fvx_open(&file, path, FA_READ | FA_WRITE | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
bdrifp = &file;
|
||||
|
||||
|
||||
if ((BDRIRead(0, sizeof(TickDBPreHeader), &pre_header) != FR_OK) ||
|
||||
!CheckDBMagic((u8*) &pre_header, true) ||
|
||||
(RemoveBDRIEntry(&(pre_header.fs_header), sizeof(TickDBPreHeader) - sizeof(BDRIFsHeader), title_id) != 0)) {
|
||||
@ -814,7 +814,7 @@ u32 RemoveTicketFromDB(const char* path, const u8* title_id) {
|
||||
bdrifp = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
fvx_close(&file);
|
||||
bdrifp = NULL;
|
||||
return 0;
|
||||
@ -823,13 +823,13 @@ u32 RemoveTicketFromDB(const char* path, const u8* title_id) {
|
||||
u32 AddTitleInfoEntryToDB(const char* path, const u8* title_id, const TitleInfoEntry* tie, bool replace) {
|
||||
FIL file;
|
||||
TitleDBPreHeader pre_header;
|
||||
|
||||
|
||||
if (fvx_open(&file, path, FA_READ | FA_WRITE | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
bdrifp = &file;
|
||||
|
||||
if ((BDRIRead(0, sizeof(TitleDBPreHeader), &pre_header) != FR_OK) ||
|
||||
|
||||
if ((BDRIRead(0, sizeof(TitleDBPreHeader), &pre_header) != FR_OK) ||
|
||||
!CheckDBMagic((u8*) &pre_header, false) ||
|
||||
(AddBDRIEntry(&(pre_header.fs_header), sizeof(TitleDBPreHeader) - sizeof(BDRIFsHeader), title_id,
|
||||
(const u8*) tie, sizeof(TitleInfoEntry), replace) != 0)) {
|
||||
@ -837,7 +837,7 @@ u32 AddTitleInfoEntryToDB(const char* path, const u8* title_id, const TitleInfoE
|
||||
bdrifp = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
fvx_close(bdrifp);
|
||||
bdrifp = NULL;
|
||||
return 0;
|
||||
@ -847,7 +847,7 @@ u32 AddTicketToDB(const char* path, const u8* title_id, const Ticket* ticket, bo
|
||||
FIL file;
|
||||
TickDBPreHeader pre_header;
|
||||
u32 entry_size = sizeof(TicketEntry) + GetTicketContentIndexSize(ticket);
|
||||
|
||||
|
||||
TicketEntry* te = (TicketEntry*)malloc(entry_size);
|
||||
if (!te) {
|
||||
return 1;
|
||||
@ -860,9 +860,9 @@ u32 AddTicketToDB(const char* path, const u8* title_id, const Ticket* ticket, bo
|
||||
free(te);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
bdrifp = &file;
|
||||
|
||||
|
||||
if ((BDRIRead(0, sizeof(TickDBPreHeader), &pre_header) != FR_OK) ||
|
||||
!CheckDBMagic((u8*) &pre_header, true) ||
|
||||
(AddBDRIEntry(&(pre_header.fs_header), sizeof(TickDBPreHeader) - sizeof(BDRIFsHeader), title_id,
|
||||
|
@ -7,7 +7,7 @@ u32 CheckBossHash(BossHeader* boss, bool encrypted) {
|
||||
u8 hash_area[0x14] __attribute__((aligned(4))) = { 0 };
|
||||
u8 boss_sha256[0x20];
|
||||
u8 l_sha256[0x20];
|
||||
|
||||
|
||||
// calculate hash
|
||||
memcpy(hash_area, ((u8*) boss) + 0x28, 0x12);
|
||||
memcpy(boss_sha256, boss->hash_header, 0x20);
|
||||
@ -16,13 +16,13 @@ u32 CheckBossHash(BossHeader* boss, bool encrypted) {
|
||||
CryptBoss(boss_sha256, 0x28 + 0x12, 0x20, boss);
|
||||
}
|
||||
sha_quick(l_sha256, hash_area, 0x14, SHA256_MODE);
|
||||
|
||||
|
||||
return (memcmp(boss_sha256, l_sha256, 0x20) == 0) ? 0 : 1;
|
||||
}
|
||||
|
||||
u32 ValidateBossHeader(BossHeader* header, u32 fsize) {
|
||||
u8 boss_magic[] = { BOSS_MAGIC };
|
||||
|
||||
|
||||
// base checks
|
||||
if ((memcmp(header->magic, boss_magic, sizeof(boss_magic)) != 0) ||
|
||||
(fsize && (fsize != getbe32(header->filesize))) ||
|
||||
@ -31,12 +31,12 @@ u32 ValidateBossHeader(BossHeader* header, u32 fsize) {
|
||||
(getbe16(header->cnthdr_hash_type) != 0x0002) ||
|
||||
(getbe16(header->cnthdr_rsa_size) != 0x0002))
|
||||
return 1;
|
||||
|
||||
|
||||
// hash check
|
||||
if ((CheckBossHash(header, false) != 0) &&
|
||||
(CheckBossHash(header, true) != 0))
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -59,14 +59,14 @@ u32 CryptBoss(void* data, u32 offset, u32 size, BossHeader* boss) {
|
||||
size -= 0x28 - offset;
|
||||
offset = 0x28;
|
||||
}
|
||||
|
||||
|
||||
// decrypt BOSS data
|
||||
u8 ctr[16] = { 0 };
|
||||
memcpy(ctr, boss->ctr12, 12);
|
||||
ctr[15] = 0x01;
|
||||
use_aeskey(0x38);
|
||||
ctr_decrypt_byte(data, data, size, offset - 0x28, AES_CNT_CTRNAND_MODE, ctr);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ u32 CryptBossSequential(void* data, u32 offset, u32 size) {
|
||||
// unexpected results otherwise
|
||||
static BossHeader boss = { 0 };
|
||||
static BossHeader* bossptr = NULL;
|
||||
|
||||
|
||||
// fetch boss header from data
|
||||
if ((offset == 0) && (size >= sizeof(BossHeader))) {
|
||||
bossptr = NULL;
|
||||
@ -87,9 +87,9 @@ u32 CryptBossSequential(void* data, u32 offset, u32 size) {
|
||||
return 1;
|
||||
bossptr = &boss;
|
||||
}
|
||||
|
||||
|
||||
// safety check, boss pointer
|
||||
if (!bossptr) return 1;
|
||||
|
||||
|
||||
return CryptBoss(data, offset, size, bossptr);
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
typedef struct {
|
||||
// actual BOSS header
|
||||
u8 magic[8]; // "boss" + 0x00010001, see above
|
||||
u8 filesize[4]; // big endian
|
||||
u8 filesize[4]; // big endian
|
||||
u8 release_date[8];
|
||||
u8 unknown0[2]; // always 0x0001
|
||||
u8 padding[2];
|
||||
@ -27,7 +27,7 @@ typedef struct {
|
||||
u8 hash_header[0x20];
|
||||
u8 signature_header[0x100];
|
||||
// payload header, first 0x1C byte used for hash (0x15A)
|
||||
u8 programId[8];
|
||||
u8 programId[8];
|
||||
u8 unknown2[4]; // typically zero
|
||||
u8 data_type[4];
|
||||
u8 size_payload[4];
|
||||
|
@ -18,20 +18,20 @@ u32 ValidateCiaHeader(CiaHeader* header) {
|
||||
|
||||
u32 GetCiaInfo(CiaInfo* info, CiaHeader* header) {
|
||||
if ((u8*) info != (u8*) header) memcpy(info, header, 0x20); // take over first 0x20 byte
|
||||
|
||||
|
||||
info->offset_cert = align(header->size_header, 64);
|
||||
info->offset_ticket = info->offset_cert + align(header->size_cert, 64);
|
||||
info->offset_tmd = info->offset_ticket + align(header->size_ticket, 64);
|
||||
info->offset_content = info->offset_tmd + align(header->size_tmd, 64);
|
||||
info->offset_meta = (header->size_meta) ? info->offset_content + align(header->size_content, 64) : 0;
|
||||
info->offset_content_list = info->offset_tmd + sizeof(TitleMetaData);
|
||||
|
||||
|
||||
info->size_content_list = info->size_tmd - sizeof(TitleMetaData);
|
||||
info->size_cia = (header->size_meta) ? info->offset_meta + info->size_meta :
|
||||
info->offset_content + info->size_content;
|
||||
|
||||
|
||||
info->max_contents = (info->size_tmd - sizeof(TitleMetaData)) / sizeof(TmdContentChunk);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ u32 BuildCiaCert(u8* ciacert) {
|
||||
0xFB, 0xD2, 0xC0, 0x47, 0x95, 0xB9, 0x4C, 0xC8, 0x0B, 0x64, 0x58, 0x96, 0xF6, 0x61, 0x0F, 0x52,
|
||||
0x18, 0x83, 0xAF, 0xE0, 0xF4, 0xE5, 0x62, 0xBA, 0x69, 0xEE, 0x72, 0x2A, 0xC2, 0x4E, 0x95, 0xB3
|
||||
};
|
||||
|
||||
|
||||
// open certs.db file on SysNAND
|
||||
FIL db;
|
||||
UINT bytes_read;
|
||||
@ -74,13 +74,13 @@ u32 BuildCiaCert(u8* ciacert) {
|
||||
f_lseek(&db, 0x3C10);
|
||||
f_read(&db, ciacert + 0x700, 0x300, &bytes_read);
|
||||
f_close(&db);
|
||||
|
||||
|
||||
// check the certificate hash
|
||||
u8 cert_hash[0x20];
|
||||
sha_quick(cert_hash, ciacert, CIA_CERT_SIZE, SHA256_MODE);
|
||||
if (memcmp(cert_hash, IS_DEVKIT ? cert_hash_expected_dev : cert_hash_expected, 0x20) != 0)
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -16,10 +16,10 @@ typedef struct {
|
||||
|
||||
u32 GetCodeLzssUncompressedSize(void* footer, u32 comp_size) {
|
||||
if (comp_size < sizeof(CodeLzssFooter)) return 0;
|
||||
|
||||
|
||||
CodeLzssFooter* f = (CodeLzssFooter*) footer;
|
||||
if ((CODE_COMP_SIZE(f) > comp_size) || (CODE_COMP_END(f) < 0)) return 0;
|
||||
|
||||
|
||||
return CODE_DEC_SIZE(f) + (comp_size - CODE_COMP_SIZE(f));
|
||||
}
|
||||
|
||||
@ -27,22 +27,22 @@ u32 GetCodeLzssUncompressedSize(void* footer, u32 comp_size) {
|
||||
u32 DecompressCodeLzss(u8* code, u32* code_size, u32 max_size) {
|
||||
u8* data_start = code;
|
||||
u8* comp_start = data_start;
|
||||
|
||||
|
||||
// get footer, fix comp_start offset
|
||||
if ((*code_size < sizeof(CodeLzssFooter)) || (*code_size > max_size)) return 1;
|
||||
CodeLzssFooter* footer = (CodeLzssFooter*) (void*) (data_start + *code_size - sizeof(CodeLzssFooter));
|
||||
if (CODE_COMP_SIZE(footer) <= *code_size) comp_start += *code_size - CODE_COMP_SIZE(footer);
|
||||
else return 1;
|
||||
|
||||
|
||||
// more sanity checks
|
||||
if ((CODE_COMP_END(footer) < 0) || (CODE_DEC_SIZE(footer) > max_size))
|
||||
return 1; // not reverse LZSS compressed code or too big uncompressed
|
||||
|
||||
|
||||
// set pointers
|
||||
u8* data_end = (u8*) comp_start + CODE_DEC_SIZE(footer);
|
||||
u8* ptr_in = (u8*) comp_start + CODE_COMP_END(footer);
|
||||
u8* ptr_out = data_end;
|
||||
|
||||
|
||||
// main decompression loop
|
||||
while ((ptr_in > comp_start) && (ptr_out > comp_start)) {
|
||||
if (!ShowProgress(data_end - ptr_out, data_end - data_start, "Decompressing .code...")) {
|
||||
@ -50,17 +50,17 @@ u32 DecompressCodeLzss(u8* code, u32* code_size, u32 max_size) {
|
||||
ShowProgress(0, data_end - data_start, "Decompressing .code...");
|
||||
ShowProgress(data_end - ptr_out, data_end - data_start, "Decompressing .code...");
|
||||
}
|
||||
|
||||
|
||||
// sanity check
|
||||
if (ptr_out < ptr_in) return 1;
|
||||
|
||||
|
||||
// read and process control byte
|
||||
u8 ctrlbyte = *(--ptr_in);
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
// end conditions met?
|
||||
if ((ptr_in <= comp_start) || (ptr_out <= comp_start))
|
||||
break;
|
||||
|
||||
|
||||
// process control byte
|
||||
if ((ctrlbyte >> i) & 0x1) {
|
||||
// control bit set, read segment code
|
||||
@ -69,11 +69,11 @@ u32 DecompressCodeLzss(u8* code, u32* code_size, u32 max_size) {
|
||||
if (ptr_in < comp_start) return 1; // corrupted code
|
||||
u32 seg_off = CODE_SEG_OFFSET(seg_code);
|
||||
u32 seg_len = CODE_SEG_SIZE(seg_code);
|
||||
|
||||
|
||||
// sanity check for output pointer
|
||||
if ((ptr_out - seg_len < comp_start) || (ptr_out + seg_off >= data_end))
|
||||
return 1;
|
||||
|
||||
|
||||
// copy data to the correct place
|
||||
for (u32 c = 0; c < seg_len; c++) {
|
||||
u8 byte = *(ptr_out + seg_off);
|
||||
@ -83,17 +83,17 @@ u32 DecompressCodeLzss(u8* code, u32* code_size, u32 max_size) {
|
||||
// sanity check for both pointers
|
||||
if ((ptr_out == comp_start) || (ptr_in == comp_start))
|
||||
return 1;
|
||||
|
||||
|
||||
// control bit not set, copy byte verbatim
|
||||
*(--ptr_out) = *(--ptr_in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check pointers
|
||||
if ((ptr_in != comp_start) || (ptr_out != comp_start))
|
||||
return 1;
|
||||
|
||||
|
||||
// all fine if arriving here - return the result
|
||||
*code_size = data_end - data_start;
|
||||
return 0;
|
||||
@ -116,7 +116,7 @@ void initTable(sCompressInfo* a_pInfo, void* a_pWork) {
|
||||
a_pInfo->ReversedOffsetTable = (s16*)(a_pWork) + 4098;
|
||||
a_pInfo->ByteTable = (s16*)(a_pWork) + 4098 + 4098;
|
||||
a_pInfo->EndTable = (s16*)(a_pWork) + 4098 + 4098 + 256;
|
||||
|
||||
|
||||
for (int i = 0; i < 256; i++) {
|
||||
a_pInfo->ByteTable[i] = -1;
|
||||
a_pInfo->EndTable[i] = -1;
|
||||
@ -127,35 +127,35 @@ int search(sCompressInfo* a_pInfo, const u8* a_pSrc, int* a_nOffset, int a_nMaxS
|
||||
if (a_nMaxSize < 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
const u8* pSearch = NULL;
|
||||
int nSize = 2;
|
||||
const u16 uWindowPos = a_pInfo->WindowPos;
|
||||
const u16 uWindowLen = a_pInfo->WindowLen;
|
||||
s16* pReversedOffsetTable = a_pInfo->ReversedOffsetTable;
|
||||
|
||||
|
||||
for (s16 nOffset = a_pInfo->EndTable[*(a_pSrc - 1)]; nOffset != -1; nOffset = pReversedOffsetTable[nOffset]) {
|
||||
if (nOffset < uWindowPos) {
|
||||
pSearch = a_pSrc + uWindowPos - nOffset;
|
||||
} else {
|
||||
pSearch = a_pSrc + uWindowLen + uWindowPos - nOffset;
|
||||
}
|
||||
|
||||
|
||||
if (pSearch - a_pSrc < 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (*(pSearch - 2) != *(a_pSrc - 2) || *(pSearch - 3) != *(a_pSrc - 3)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
int nMaxSize = (int)((s64)min(a_nMaxSize, pSearch - a_pSrc));
|
||||
int nCurrentSize = 3;
|
||||
|
||||
|
||||
while (nCurrentSize < nMaxSize && *(pSearch - nCurrentSize - 1) == *(a_pSrc - nCurrentSize - 1)) {
|
||||
nCurrentSize++;
|
||||
}
|
||||
|
||||
|
||||
if (nCurrentSize > nSize) {
|
||||
nSize = nCurrentSize;
|
||||
*a_nOffset = (int)(pSearch - a_pSrc);
|
||||
@ -164,11 +164,11 @@ int search(sCompressInfo* a_pInfo, const u8* a_pSrc, int* a_nOffset, int a_nMaxS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (nSize < 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
return nSize;
|
||||
}
|
||||
|
||||
@ -181,33 +181,33 @@ void slideByte(sCompressInfo* a_pInfo, const u8* a_pSrc) {
|
||||
s16* pReversedOffsetTable = a_pInfo->ReversedOffsetTable;
|
||||
s16* pByteTable = a_pInfo->ByteTable;
|
||||
s16* pEndTable = a_pInfo->EndTable;
|
||||
|
||||
|
||||
if (uWindowLen == 4098) {
|
||||
u8 uOutData = *(a_pSrc + 4097);
|
||||
|
||||
|
||||
if ((pByteTable[uOutData] = pOffsetTable[pByteTable[uOutData]]) == -1) {
|
||||
pEndTable[uOutData] = -1;
|
||||
} else {
|
||||
pReversedOffsetTable[pByteTable[uOutData]] = -1;
|
||||
}
|
||||
|
||||
|
||||
uInsertOffset = uWindowPos;
|
||||
} else {
|
||||
uInsertOffset = uWindowLen;
|
||||
}
|
||||
|
||||
|
||||
s16 nOffset = pEndTable[uInData];
|
||||
|
||||
|
||||
if (nOffset == -1) {
|
||||
pByteTable[uInData] = uInsertOffset;
|
||||
} else {
|
||||
pOffsetTable[nOffset] = uInsertOffset;
|
||||
}
|
||||
|
||||
|
||||
pEndTable[uInData] = uInsertOffset;
|
||||
pOffsetTable[uInsertOffset] = -1;
|
||||
pReversedOffsetTable[uInsertOffset] = nOffset;
|
||||
|
||||
|
||||
if (uWindowLen == 4098) {
|
||||
a_pInfo->WindowPos = (uWindowPos + 1) % 4098;
|
||||
} else {
|
||||
@ -228,19 +228,19 @@ s64 alignBytes(s64 a_nData, s64 a_nAlignment) {
|
||||
bool CompressCodeLzss(const u8* a_pUncompressed, u32 a_uUncompressedSize, u8* a_pCompressed, u32* a_uCompressedSize) {
|
||||
const int s_nCompressWorkSize = (4098 + 4098 + 256 + 256) * sizeof(s16);
|
||||
bool bResult = true;
|
||||
|
||||
|
||||
if (a_uUncompressedSize > sizeof(CodeLzssFooter) && *a_uCompressedSize >= a_uUncompressedSize) {
|
||||
u8* pWork = malloc(s_nCompressWorkSize * sizeof(u8));
|
||||
if (!pWork) return false;
|
||||
|
||||
|
||||
do {
|
||||
sCompressInfo info;
|
||||
initTable(&info, pWork);
|
||||
|
||||
|
||||
const int nMaxSize = 0xF + 3;
|
||||
const u8* pSrc = a_pUncompressed + a_uUncompressedSize;
|
||||
u8* pDest = a_pCompressed + a_uUncompressedSize;
|
||||
|
||||
|
||||
while (pSrc - a_pUncompressed > 0 && pDest - a_pCompressed > 0) {
|
||||
if (!ShowProgress((u32)(a_pUncompressed + a_uUncompressedSize - pSrc), a_uUncompressedSize, "Compressing .code...")) {
|
||||
if (ShowPrompt(true, "Compressing .code...\nB button detected. Cancel?")) {
|
||||
@ -250,20 +250,20 @@ bool CompressCodeLzss(const u8* a_pUncompressed, u32 a_uUncompressedSize, u8* a_
|
||||
ShowProgress(0, a_uUncompressedSize, "Compressing .code...");
|
||||
ShowProgress((u32)(a_pUncompressed + a_uUncompressedSize - pSrc), a_uUncompressedSize, "Compressing .code...");
|
||||
}
|
||||
|
||||
|
||||
u8* pFlag = --pDest;
|
||||
*pFlag = 0;
|
||||
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int nOffset = 0;
|
||||
int nSize = search(&info, pSrc, &nOffset, (int)((s64)min((s64)min(nMaxSize, pSrc - a_pUncompressed), a_pUncompressed + a_uUncompressedSize - pSrc)));
|
||||
|
||||
|
||||
if (nSize < 3) {
|
||||
if (pDest - a_pCompressed < 1) {
|
||||
bResult = false;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
slide(&info, pSrc, 1);
|
||||
*--pDest = *--pSrc;
|
||||
} else {
|
||||
@ -271,7 +271,7 @@ bool CompressCodeLzss(const u8* a_pUncompressed, u32 a_uUncompressedSize, u8* a_
|
||||
bResult = false;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
*pFlag |= 0x80 >> i;
|
||||
slide(&info, pSrc, nSize);
|
||||
pSrc -= nSize;
|
||||
@ -279,29 +279,29 @@ bool CompressCodeLzss(const u8* a_pUncompressed, u32 a_uUncompressedSize, u8* a_
|
||||
*--pDest = (nSize << 4 & 0xF0) | ((nOffset - 3) >> 8 & 0x0F);
|
||||
*--pDest = (nOffset - 3) & 0xFF;
|
||||
}
|
||||
|
||||
|
||||
if (pSrc - a_pUncompressed <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!bResult) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!bResult) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
*a_uCompressedSize = (u32)(a_pCompressed + a_uUncompressedSize - pDest);
|
||||
} while (false);
|
||||
|
||||
|
||||
free(pWork);
|
||||
} else {
|
||||
bResult = false;
|
||||
}
|
||||
|
||||
|
||||
if (bResult) {
|
||||
u32 uOrigSize = a_uUncompressedSize;
|
||||
u8* pCompressBuffer = a_pCompressed + a_uUncompressedSize - *a_uCompressedSize;
|
||||
@ -309,10 +309,10 @@ bool CompressCodeLzss(const u8* a_pUncompressed, u32 a_uUncompressedSize, u8* a_
|
||||
u32 uOrigSafe = 0;
|
||||
u32 uCompressSafe = 0;
|
||||
bool bOver = false;
|
||||
|
||||
|
||||
while (uOrigSize > 0) {
|
||||
u8 uFlag = pCompressBuffer[--uCompressBufferSize];
|
||||
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((uFlag << i & 0x80) == 0) {
|
||||
uCompressBufferSize--;
|
||||
@ -321,7 +321,7 @@ bool CompressCodeLzss(const u8* a_pUncompressed, u32 a_uUncompressedSize, u8* a_
|
||||
int nSize = (pCompressBuffer[--uCompressBufferSize] >> 4 & 0x0F) + 3;
|
||||
uCompressBufferSize--;
|
||||
uOrigSize -= nSize;
|
||||
|
||||
|
||||
if (uOrigSize < uCompressBufferSize) {
|
||||
uOrigSafe = uOrigSize;
|
||||
uCompressSafe = uCompressBufferSize;
|
||||
@ -329,24 +329,24 @@ bool CompressCodeLzss(const u8* a_pUncompressed, u32 a_uUncompressedSize, u8* a_
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (uOrigSize <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (bOver) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
u32 uCompressedSize = *a_uCompressedSize - uCompressSafe;
|
||||
u32 uPadOffset = uOrigSafe + uCompressedSize;
|
||||
u32 uCompFooterOffset = (u32)(alignBytes(uPadOffset, 4));
|
||||
*a_uCompressedSize = uCompFooterOffset + sizeof(CodeLzssFooter);
|
||||
u32 uTop = *a_uCompressedSize - uOrigSafe;
|
||||
u32 uBottom = *a_uCompressedSize - uPadOffset;
|
||||
|
||||
|
||||
if (*a_uCompressedSize >= a_uUncompressedSize || uTop > 0xFFFFFF) {
|
||||
bResult = false;
|
||||
} else {
|
||||
@ -358,6 +358,6 @@ bool CompressCodeLzss(const u8* a_pUncompressed, u32 a_uUncompressedSize, u8* a_
|
||||
pCompFooter->addsize_dec = a_uUncompressedSize - *a_uCompressedSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
@ -107,13 +107,13 @@ inline static u32 DisaDiffSize(const TCHAR* path) {
|
||||
|
||||
inline static FRESULT DisaDiffOpen(const TCHAR* path) {
|
||||
FRESULT res = FR_OK;
|
||||
|
||||
|
||||
ddfp = NULL;
|
||||
if (path) {
|
||||
res = fvx_open(&ddfile, path, FA_READ | FA_WRITE | FA_OPEN_EXISTING);
|
||||
if (res == FR_OK) ddfp = &ddfile;
|
||||
} else if (!GetMountState()) res = FR_DENIED;
|
||||
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -164,16 +164,16 @@ u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB) {
|
||||
static const u8 ivfc_magic[] = { IVFC_MAGIC };
|
||||
static const u8 dpfs_magic[] = { DPFS_MAGIC };
|
||||
static const u8 difi_magic[] = { DIFI_MAGIC };
|
||||
|
||||
|
||||
// reset reader info
|
||||
memset(info, 0x00, sizeof(DisaDiffRWInfo));
|
||||
|
||||
|
||||
// get file size, header at header offset
|
||||
u32 file_size = DisaDiffSize(path);
|
||||
u8 header[0x100];
|
||||
if (DisaDiffQRead(path, header, 0x100, 0x100) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
// DISA/DIFF header: find partition offset & size and DIFI descriptor
|
||||
u32 offset_partition = 0;
|
||||
u32 size_partition = 0;
|
||||
@ -207,22 +207,22 @@ u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB) {
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// check the output so far
|
||||
if (!offset_difi || (offset_difi + sizeof(DifiStruct) > file_size) || (offset_partition + size_partition > file_size))
|
||||
return 1;
|
||||
|
||||
|
||||
info->offset_difi = offset_difi;
|
||||
// read DIFI struct from file
|
||||
const DifiStruct difis;
|
||||
if (DisaDiffQRead(path, (DifiStruct*) &difis, offset_difi, sizeof(DifiStruct)) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
if ((memcmp(difis.difi.magic, difi_magic, 8) != 0) ||
|
||||
(memcmp(difis.ivfc.magic, ivfc_magic, 8) != 0) ||
|
||||
(memcmp(difis.dpfs.magic, dpfs_magic, 8) != 0))
|
||||
return 1;
|
||||
|
||||
|
||||
// check & get data from DIFI header
|
||||
const DifiHeader* difi = &(difis.difi);
|
||||
if ((difi->offset_ivfc != sizeof(DifiHeader)) ||
|
||||
@ -232,12 +232,12 @@ u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB) {
|
||||
(difi->offset_hash != difi->offset_dpfs + difi->size_dpfs) ||
|
||||
(difi->size_hash < 0x20))
|
||||
return 1;
|
||||
|
||||
|
||||
info->dpfs_lvl1_selector = difi->dpfs_lvl1_selector;
|
||||
info->ivfc_use_extlvl4 = difi->ivfc_use_extlvl4;
|
||||
info->offset_ivfc_lvl4 = (u32) (offset_partition + difi->ivfc_offset_extlvl4);
|
||||
info->offset_master_hash = (u32) difi->offset_hash;
|
||||
|
||||
|
||||
// check & get data from DPFS descriptor
|
||||
const DpfsDescriptor* dpfs = &(difis.dpfs);
|
||||
if ((dpfs->offset_lvl1 + dpfs->size_lvl1 > dpfs->offset_lvl2) ||
|
||||
@ -246,7 +246,7 @@ u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB) {
|
||||
(2 > dpfs->log_lvl2) || (dpfs->log_lvl2 > dpfs->log_lvl3) ||
|
||||
!dpfs->size_lvl1 || !dpfs->size_lvl2 || !dpfs->size_lvl3)
|
||||
return 1;
|
||||
|
||||
|
||||
info->offset_dpfs_lvl1 = (u32) (offset_partition + dpfs->offset_lvl1);
|
||||
info->offset_dpfs_lvl2 = (u32) (offset_partition + dpfs->offset_lvl2);
|
||||
info->offset_dpfs_lvl3 = (u32) (offset_partition + dpfs->offset_lvl3);
|
||||
@ -255,7 +255,7 @@ u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB) {
|
||||
info->size_dpfs_lvl3 = (u32) dpfs->size_lvl3;
|
||||
info->log_dpfs_lvl2 = (u32) dpfs->log_lvl2;
|
||||
info->log_dpfs_lvl3 = (u32) dpfs->log_lvl3;
|
||||
|
||||
|
||||
// check & get data from IVFC descriptor
|
||||
const IvfcDescriptor* ivfc = &(difis.ivfc);
|
||||
if ((ivfc->size_hash != difi->size_hash) ||
|
||||
@ -264,16 +264,16 @@ u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB) {
|
||||
(ivfc->offset_lvl2 + ivfc->size_lvl2 > ivfc->offset_lvl3) ||
|
||||
(ivfc->offset_lvl3 + ivfc->size_lvl3 > dpfs->size_lvl3))
|
||||
return 1;
|
||||
|
||||
|
||||
if (!info->ivfc_use_extlvl4) {
|
||||
if ((ivfc->offset_lvl3 + ivfc->size_lvl3 > ivfc->offset_lvl4) ||
|
||||
(ivfc->offset_lvl4 + ivfc->size_lvl4 > dpfs->size_lvl3))
|
||||
return 1;
|
||||
|
||||
|
||||
info->offset_ivfc_lvl4 = (u32) ivfc->offset_lvl4;
|
||||
} else if (info->offset_ivfc_lvl4 + ivfc->size_lvl4 > offset_partition + size_partition)
|
||||
return 1;
|
||||
|
||||
|
||||
info->log_ivfc_lvl1 = (u32) ivfc->log_lvl1;
|
||||
info->log_ivfc_lvl2 = (u32) ivfc->log_lvl2;
|
||||
info->log_ivfc_lvl3 = (u32) ivfc->log_lvl3;
|
||||
@ -285,7 +285,7 @@ u32 GetDisaDiffRWInfo(const char* path, DisaDiffRWInfo* info, bool partitionB) {
|
||||
info->size_ivfc_lvl2 = (u32) ivfc->size_lvl2;
|
||||
info->size_ivfc_lvl3 = (u32) ivfc->size_lvl3;
|
||||
info->size_ivfc_lvl4 = (u32) ivfc->size_lvl4;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -293,31 +293,31 @@ u32 BuildDisaDiffDpfsLvl2Cache(const char* path, const DisaDiffRWInfo* info, u8*
|
||||
const u32 min_cache_bits = (info->size_dpfs_lvl3 + (1 << info->log_dpfs_lvl3) - 1) >> info->log_dpfs_lvl3;
|
||||
const u32 min_cache_size = ((min_cache_bits + 31) >> (3 + 2)) << 2;
|
||||
const u32 offset_lvl1 = info->offset_dpfs_lvl1 + ((info->dpfs_lvl1_selector) ? info->size_dpfs_lvl1 : 0);
|
||||
|
||||
|
||||
// safety (this still assumes all the checks from GetDisaDiffRWInfo())
|
||||
if ((cache_size < min_cache_size) ||
|
||||
(min_cache_size > info->size_dpfs_lvl2) ||
|
||||
(min_cache_size > (info->size_dpfs_lvl1 << (3 + info->log_dpfs_lvl2)))) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// allocate memory
|
||||
u8* lvl1 = (u8*) malloc(info->size_dpfs_lvl1);
|
||||
if (!lvl1) return 1; // this is never more than 8 byte in reality -___-
|
||||
|
||||
|
||||
// open file pointer
|
||||
if (DisaDiffOpen(path) != FR_OK) {
|
||||
free(lvl1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// read lvl1
|
||||
u32 ret = 0;
|
||||
if ((ret != 0) || DisaDiffRead(lvl1, info->size_dpfs_lvl1, offset_lvl1)) ret = 1;
|
||||
|
||||
|
||||
// read full lvl2_0 to cache
|
||||
if ((ret != 0) || DisaDiffRead(cache, info->size_dpfs_lvl2, info->offset_dpfs_lvl2)) ret = 1;
|
||||
|
||||
|
||||
// cherry-pick lvl2_1
|
||||
u32 log_lvl2 = info->log_dpfs_lvl2;
|
||||
u32 offset_lvl2_1 = info->offset_dpfs_lvl2 + info->size_dpfs_lvl2;
|
||||
@ -330,7 +330,7 @@ u32 BuildDisaDiffDpfsLvl2Cache(const char* path, const DisaDiffRWInfo* info, u8*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
((DisaDiffRWInfo*) info)->dpfs_lvl2_cache = cache;
|
||||
free(lvl1);
|
||||
DisaDiffClose();
|
||||
@ -344,11 +344,11 @@ static u32 ReadDisaDiffDpfsLvl3(const DisaDiffRWInfo* info, u32 offset, u32 size
|
||||
const u32 offset_lvl3_0 = info->offset_dpfs_lvl3;
|
||||
const u32 offset_lvl3_1 = offset_lvl3_0 + info->size_dpfs_lvl3;
|
||||
const u32 log_lvl3 = info->log_dpfs_lvl3;
|
||||
|
||||
|
||||
u32 read_start = offset_start;
|
||||
u32 read_end = read_start;
|
||||
u32 bit_state = 0;
|
||||
|
||||
|
||||
// full reading below
|
||||
while (size && (read_start < offset_end)) {
|
||||
// read bits until bit_state does not match
|
||||
@ -370,7 +370,7 @@ static u32 ReadDisaDiffDpfsLvl3(const DisaDiffRWInfo* info, u32 offset, u32 size
|
||||
// flip the bit_state
|
||||
bit_state = ~bit_state & 0x1;
|
||||
}
|
||||
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@ -381,11 +381,11 @@ static u32 WriteDisaDiffDpfsLvl3(const DisaDiffRWInfo* info, u32 offset, u32 siz
|
||||
const u32 offset_lvl3_0 = info->offset_dpfs_lvl3;
|
||||
const u32 offset_lvl3_1 = offset_lvl3_0 + info->size_dpfs_lvl3;
|
||||
const u32 log_lvl3 = info->log_dpfs_lvl3;
|
||||
|
||||
|
||||
u32 write_start = offset_start;
|
||||
u32 write_end = write_start;
|
||||
u32 bit_state = 0;
|
||||
|
||||
|
||||
// full reading below
|
||||
while (size && (write_start < offset_end)) {
|
||||
// write bits until bit_state does not match
|
||||
@ -407,40 +407,40 @@ static u32 WriteDisaDiffDpfsLvl3(const DisaDiffRWInfo* info, u32 offset, u32 siz
|
||||
// flip the bit_state
|
||||
bit_state = ~bit_state & 0x1;
|
||||
}
|
||||
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
u32 FixDisaDiffPartitionHash(const DisaDiffRWInfo* info) {
|
||||
const u32 size = info->size_table;
|
||||
u8 sha_buf[0x20];
|
||||
u8* buf;
|
||||
|
||||
|
||||
if (!(buf = malloc(size)))
|
||||
return 1;
|
||||
|
||||
|
||||
if (DisaDiffRead(buf, size, info->offset_table) != FR_OK) {
|
||||
free(buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
sha_quick(sha_buf, buf, size, SHA256_MODE);
|
||||
|
||||
|
||||
free(buf);
|
||||
|
||||
|
||||
if (DisaDiffWrite(sha_buf, 0x20, info->offset_partition_hash) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 FixDisaDiffIvfcLevel(const DisaDiffRWInfo* info, u32 level, u32 offset, u32 size, u32* next_offset, u32* next_size) {
|
||||
if (level == 0)
|
||||
return FixDisaDiffPartitionHash(info);
|
||||
|
||||
|
||||
if (level > 4)
|
||||
return 1;
|
||||
|
||||
|
||||
const u32 offset_ivfc_lvl = (&(info->offset_ivfc_lvl1))[level - 1];
|
||||
const u32 size_ivfc_lvl = (&(info->size_ivfc_lvl1))[level - 1];
|
||||
const u32 log_ivfc_lvl = (&(info->log_ivfc_lvl1))[level - 1];
|
||||
@ -448,48 +448,48 @@ u32 FixDisaDiffIvfcLevel(const DisaDiffRWInfo* info, u32 level, u32 offset, u32
|
||||
u32 read_size = block_size;
|
||||
u32 align_offset = (offset >> log_ivfc_lvl) << log_ivfc_lvl; // align starting offset
|
||||
u32 align_size = size + offset - align_offset; // increase size by the amount starting offset decreased when aligned
|
||||
|
||||
|
||||
if (level != 1) {
|
||||
if (next_offset) *next_offset = (align_offset >> log_ivfc_lvl) * 0x20;
|
||||
if (next_size) *next_size = ((align_size >> log_ivfc_lvl) + (((align_size % block_size) == 0) ? 0 : 1)) * 0x20;
|
||||
}
|
||||
|
||||
|
||||
u8 sha_buf[0x20];
|
||||
u8* buf;
|
||||
|
||||
|
||||
if (!(buf = malloc(block_size)))
|
||||
return 1;
|
||||
|
||||
|
||||
while (align_size > 0) {
|
||||
if (align_offset + block_size > size_ivfc_lvl) {
|
||||
memset(buf, 0, block_size);
|
||||
read_size -= (align_offset + block_size - size_ivfc_lvl);
|
||||
}
|
||||
|
||||
|
||||
if (((level == 4) && info->ivfc_use_extlvl4) ? (DisaDiffRead(buf, read_size, align_offset + offset_ivfc_lvl) != FR_OK) :
|
||||
(ReadDisaDiffDpfsLvl3(info, align_offset + offset_ivfc_lvl, read_size, buf) != read_size)) {
|
||||
free(buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
sha_quick(sha_buf, buf, block_size, SHA256_MODE);
|
||||
|
||||
|
||||
if ((level == 1) ? (DisaDiffWrite(sha_buf, 0x20, info->offset_difi + info->offset_master_hash + ((align_offset >> log_ivfc_lvl) * 0x20)) != FR_OK) :
|
||||
|
||||
|
||||
if ((level == 1) ? (DisaDiffWrite(sha_buf, 0x20, info->offset_difi + info->offset_master_hash + ((align_offset >> log_ivfc_lvl) * 0x20)) != FR_OK) :
|
||||
(WriteDisaDiffDpfsLvl3(info, (&(info->offset_ivfc_lvl1))[level - 2] + ((align_offset >> log_ivfc_lvl) * 0x20), 0x20, sha_buf) != 0x20)) {
|
||||
free(buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
align_offset += block_size;
|
||||
align_size = ((align_size < block_size) ? 0 : (align_size - block_size));
|
||||
}
|
||||
|
||||
|
||||
free(buf);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
u32 ReadDisaDiffIvfcLvl4(const char* path, const DisaDiffRWInfo* info, u32 offset, u32 size, void* buffer) { // offset: offset inside IVFC lvl4
|
||||
// DisaDiffRWInfo not provided?
|
||||
DisaDiffRWInfo info_l;
|
||||
@ -504,15 +504,15 @@ u32 ReadDisaDiffIvfcLvl4(const char* path, const DisaDiffRWInfo* info, u32 offse
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// open file pointer
|
||||
if (DisaDiffOpen(path) != FR_OK)
|
||||
size = 0;
|
||||
|
||||
|
||||
// sanity checks - offset & size
|
||||
if (offset > info->size_ivfc_lvl4) return 0;
|
||||
else if (offset + size > info->size_ivfc_lvl4) size = info->size_ivfc_lvl4 - offset;
|
||||
|
||||
|
||||
if (info->ivfc_use_extlvl4) {
|
||||
if (DisaDiffRead(buffer, size, info->offset_ivfc_lvl4 + offset) != FR_OK)
|
||||
size = 0;
|
||||
@ -541,22 +541,22 @@ u32 WriteDisaDiffIvfcLvl4(const char* path, const DisaDiffRWInfo* info, u32 offs
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// sanity check - offset & size
|
||||
if (offset + size > info->size_ivfc_lvl4)
|
||||
return 0;
|
||||
|
||||
|
||||
// open file pointer
|
||||
if (DisaDiffOpen(path) != FR_OK)
|
||||
size = 0;
|
||||
|
||||
|
||||
if (info->ivfc_use_extlvl4) {
|
||||
if (DisaDiffWrite(buffer, size, info->offset_ivfc_lvl4 + offset) != FR_OK)
|
||||
size = 0;
|
||||
} else {
|
||||
size = WriteDisaDiffDpfsLvl3(info, info->offset_ivfc_lvl4 + offset, size, buffer);
|
||||
}
|
||||
|
||||
|
||||
if ((size != 0) && ddfp) { // if we're writing to a mounted image, the hash chain will be handled later by vdisadiff
|
||||
u32 hashfix_offset = offset, hashfix_size = size;
|
||||
for (int i = 4; i >= 0; i--) {
|
||||
@ -566,7 +566,7 @@ u32 WriteDisaDiffIvfcLvl4(const char* path, const DisaDiffRWInfo* info, u32 offs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DisaDiffClose();
|
||||
if (cache) free(cache);
|
||||
return size;
|
||||
|
@ -169,10 +169,10 @@ u32 SetupSecretKey(u32 keynum) {
|
||||
static u8 __attribute__((aligned(32))) sector[0x200];
|
||||
static u32 got_keys = 0;
|
||||
u8* key = sector + (keynum*0x10);
|
||||
|
||||
|
||||
if (keynum >= 0x200/0x10)
|
||||
return 1; // safety
|
||||
|
||||
|
||||
// try to load full secret sector
|
||||
if (!got_keys) {
|
||||
ReadNandSectors(sector, 0x96, 1, 0x11, NAND_SYSNAND);
|
||||
@ -185,34 +185,34 @@ u32 SetupSecretKey(u32 keynum) {
|
||||
if (LoadKeyFromFile(key, 0x11, 'N', (keynum == 0) ? "95" : "96") == 0)
|
||||
got_keys |= (0x1<<keynum); // got at least this key
|
||||
}
|
||||
|
||||
|
||||
// setup the key
|
||||
if (got_keys & (0x1<<keynum)) {
|
||||
setup_aeskey(0x11, key);
|
||||
use_aeskey(0x11);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// out of options
|
||||
return 1;
|
||||
}
|
||||
|
||||
u32 DecryptA9LHeader(FirmA9LHeader* header) {
|
||||
u32 type = A9L_CRYPTO_TYPE(header);
|
||||
|
||||
|
||||
if (SetupSecretKey(0) != 0) return 1;
|
||||
aes_decrypt(header->keyX0x15, header->keyX0x15, 1, AES_CNT_ECB_DECRYPT_MODE);
|
||||
if (type) {
|
||||
if (SetupSecretKey((type == 1) ? 0 : 1) != 0) return 1;
|
||||
aes_decrypt(header->keyX0x16, header->keyX0x16, 1, AES_CNT_ECB_DECRYPT_MODE);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 SetupArm9BinaryCrypto(FirmA9LHeader* header) {
|
||||
u32 type = A9L_CRYPTO_TYPE(header);
|
||||
|
||||
|
||||
if (type == 0) {
|
||||
u8 __attribute__((aligned(32))) keyX0x15[0x10];
|
||||
memcpy(keyX0x15, header->keyX0x15, 0x10);
|
||||
@ -230,24 +230,24 @@ u32 SetupArm9BinaryCrypto(FirmA9LHeader* header) {
|
||||
setup_aeskeyY(0x16, header->keyY0x150x16);
|
||||
use_aeskey(0x16);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 DecryptArm9Binary(void* data, u32 offset, u32 size, FirmA9LHeader* a9l) {
|
||||
// offset == offset inside ARM9 binary
|
||||
// ARM9 binary begins 0x800 byte after the ARM9 loader header
|
||||
|
||||
|
||||
// only process actual ARM9 binary
|
||||
u32 size_bin = GetArm9BinarySize(a9l);
|
||||
if (offset >= size_bin) return 0;
|
||||
else if (size >= size_bin - offset)
|
||||
size = size_bin - offset;
|
||||
|
||||
|
||||
// decrypt data
|
||||
if (SetupArm9BinaryCrypto(a9l) != 0) return 1;
|
||||
ctr_decrypt_byte(data, data, size, offset, AES_CNT_CTRNAND_MODE, a9l->ctr);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -256,16 +256,16 @@ u32 DecryptFirm(void* data, u32 offset, u32 size, FirmHeader* firm, FirmA9LHeade
|
||||
FirmSectionHeader* arm9s = FindFirmArm9Section(firm);
|
||||
u32 offset_arm9bin = arm9s->offset + ARM9BIN_OFFSET;
|
||||
u32 size_arm9bin = GetArm9BinarySize(a9l);
|
||||
|
||||
|
||||
// sanity checks
|
||||
if (!size_arm9bin || (size_arm9bin + ARM9BIN_OFFSET > arm9s->size))
|
||||
return 1; // bad header / data
|
||||
|
||||
|
||||
// check if ARM9 binary in data
|
||||
if ((offset_arm9bin >= offset + size) ||
|
||||
(offset >= offset_arm9bin + size_arm9bin))
|
||||
return 0; // section not in data
|
||||
|
||||
|
||||
// determine data / offset / size
|
||||
u8* data8 = (u8*)data;
|
||||
u8* data_i = data8;
|
||||
@ -277,7 +277,7 @@ u32 DecryptFirm(void* data, u32 offset, u32 size, FirmHeader* firm, FirmA9LHeade
|
||||
size_i = size_arm9bin - offset_i;
|
||||
if (size_i > size - (data_i - data8))
|
||||
size_i = size - (data_i - data8);
|
||||
|
||||
|
||||
return DecryptArm9Binary(data_i, offset_i, size_i, a9l);
|
||||
}
|
||||
|
||||
@ -290,7 +290,7 @@ u32 DecryptFirmSequential(void* data, u32 offset, u32 size) {
|
||||
static FirmHeader* firmptr = NULL;
|
||||
static FirmA9LHeader* a9lptr = NULL;
|
||||
static FirmSectionHeader* arm9s = NULL;
|
||||
|
||||
|
||||
// fetch firm header from data
|
||||
if ((offset == 0) && (size >= sizeof(FirmHeader))) {
|
||||
memcpy(&firm, data, sizeof(FirmHeader));
|
||||
@ -298,17 +298,17 @@ u32 DecryptFirmSequential(void* data, u32 offset, u32 size) {
|
||||
arm9s = (firmptr) ? FindFirmArm9Section(firmptr) : NULL;
|
||||
a9lptr = NULL;
|
||||
}
|
||||
|
||||
|
||||
// safety check, firm header pointer
|
||||
if (!firmptr) return 1;
|
||||
|
||||
|
||||
// fetch ARM9 loader header from data
|
||||
if (arm9s && !a9lptr && (offset <= arm9s->offset) &&
|
||||
((offset + size) >= arm9s->offset + sizeof(FirmA9LHeader))) {
|
||||
memcpy(&a9l, (u8*)data + arm9s->offset - offset, sizeof(FirmA9LHeader));
|
||||
a9lptr = (ValidateFirmA9LHeader(&a9l) == 0) ? &a9l : NULL;
|
||||
}
|
||||
|
||||
|
||||
return (a9lptr) ? DecryptFirm(data, offset, size, firmptr, a9lptr) : 0;
|
||||
}
|
||||
|
||||
@ -318,17 +318,17 @@ u32 DecryptFirmFull(void* data, u32 size) {
|
||||
FirmSectionHeader* arm9s = FindFirmArm9Section(firm);
|
||||
if (ValidateFirmHeader(firm, size) != 0) return 1; // not a proper firm
|
||||
if (!arm9s) return 0; // no ARM9 section -> not encrypted -> done
|
||||
|
||||
|
||||
FirmA9LHeader* a9l = (FirmA9LHeader*)(void*) ((u8*) data + arm9s->offset);
|
||||
if (ValidateFirmA9LHeader(a9l) != 0) return 0; // no ARM9bin -> not encrypted -> done
|
||||
|
||||
|
||||
// decrypt FIRM and ARM9loader header
|
||||
if ((DecryptFirm(data, 0, size, firm, a9l) != 0) || (DecryptA9LHeader(a9l) != 0))
|
||||
return 1;
|
||||
|
||||
|
||||
// fix ARM9 section SHA and ARM9 entrypoint
|
||||
sha_quick(arm9s->hash, (u8*) data + arm9s->offset, arm9s->size, SHA256_MODE);
|
||||
firm->entry_arm9 = ARM9ENTRY_FIX(firm);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -8,20 +8,20 @@
|
||||
|
||||
u32 ValidateAgbSaveHeader(AgbSaveHeader* header) {
|
||||
static u8 magic[] = { AGBSAVE_MAGIC };
|
||||
|
||||
|
||||
// basic checks
|
||||
if ((memcmp(header->magic, magic, sizeof(magic)) != 0) ||
|
||||
(header->unknown0 != 1) || (header->save_start != 0x200) ||
|
||||
(header->save_size > AGBSAVE_MAX_SSIZE) || !(GBASAVE_VALID(header->save_size)))
|
||||
return 1;
|
||||
|
||||
|
||||
// reserved area checks
|
||||
// disabled due to a weird quirk https://github.com/d0k3/GodMode9/issues/412
|
||||
// for (u32 i = 0; i < sizeof(header->reserved0); i++) if (header->reserved0[i] != 0xFF) return 1;
|
||||
// for (u32 i = 0; i < sizeof(header->reserved1); i++) if (header->reserved1[i] != 0xFF) return 1;
|
||||
// for (u32 i = 0; i < sizeof(header->reserved2); i++) if (header->reserved2[i] != 0xFF) return 1;
|
||||
// for (u32 i = 0; i < sizeof(header->reserved3); i++) if (header->reserved3[i] != 0xFF) return 1;
|
||||
|
||||
|
||||
// all fine if arriving here
|
||||
return 0;
|
||||
}
|
||||
@ -30,60 +30,60 @@ u32 ValidateAgbSaveHeader(AgbSaveHeader* header) {
|
||||
u32 ValidateAgbHeader(AgbHeader* agb) {
|
||||
static const u8 logo_sha[0x20] = { AGBLOGO_SHA256 };
|
||||
u8 logo[0x9C] __attribute__((aligned(4)));
|
||||
|
||||
|
||||
// check fixed value
|
||||
if (agb->fixed != 0x96) return 1;
|
||||
|
||||
|
||||
// header checksum
|
||||
u8* hdr = (u8*) agb;
|
||||
u8 checksum = 0x00 - 0x19;
|
||||
for (u32 i = 0xA0; i < 0xBD; i++)
|
||||
checksum -= hdr[i];
|
||||
if (agb->checksum != checksum) return 1;
|
||||
|
||||
|
||||
// logo SHA check
|
||||
memcpy(logo, agb->logo, 0x9C);
|
||||
logo[0x98] &= ~0x84;
|
||||
logo[0x98] &= ~0x84;
|
||||
logo[0x9A] &= ~0x03;
|
||||
if (sha_cmp(logo_sha, logo, 0x9C, SHA256_MODE) != 0)
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* u32 ValidateAgbVc(void* data, u32 len) {
|
||||
const u8 magic[] = { GBAVC_MAGIC };
|
||||
|
||||
|
||||
if (len < sizeof(AgbHeader) + sizeof(AgbVcFooter))
|
||||
return 1;
|
||||
|
||||
|
||||
AgbHeader* header = (AgbHeader*) data;
|
||||
AgbVcFooter* footer = (AgbVcFooter*) (((u8*) data) + len - sizeof(AgbVcFooter));
|
||||
|
||||
|
||||
if ((ValidateAgbHeader(header) != 0) || (memcmp(footer->magic, magic, sizeof(magic)) != 0) ||
|
||||
(footer->rom_size != len - sizeof(AgbVcFooter)))
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
} */
|
||||
|
||||
// basically reverse ValidateAgbSaveHeader()
|
||||
/* u32 BuildAgbSaveHeader(AgbSaveHeader* header, u64 title_id, u32 save_size) {
|
||||
const u8 magic[] = { AGBSAVE_MAGIC };
|
||||
|
||||
|
||||
memset(header, 0x00, sizeof(AgbSaveHeader));
|
||||
memset(header->reserved0, 0xFF, sizeof(header->reserved0));
|
||||
memset(header->reserved1, 0xFF, sizeof(header->reserved1));
|
||||
memset(header->reserved2, 0xFF, sizeof(header->reserved2));
|
||||
memset(header->reserved3, 0xFF, sizeof(header->reserved3));
|
||||
|
||||
|
||||
memcpy(header->magic, magic, sizeof(magic));
|
||||
header->unknown0 = 0x01;
|
||||
header->title_id = title_id;
|
||||
header->save_start = 0x200;
|
||||
header->save_size = save_size;
|
||||
|
||||
|
||||
sdmmc_get_cid(0, (u32*) (void*) &(header->sd_cid));
|
||||
|
||||
|
||||
return 0;
|
||||
} */
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
|
||||
|
||||
#define GBAVC_MAGIC '.', 'C', 'A', 'A'
|
||||
#define AGBSAVE_MAGIC '.', 'S', 'A', 'V'
|
||||
#define AGBSAVE_MAX_SIZE (0x000180 * 0x200) // standard size of the NAND partition
|
||||
@ -20,14 +20,14 @@
|
||||
((tp >= 0x4) && (tp <= 0x9)) ? GBASAVE_FLASH_64K : \
|
||||
((tp >= 0xA) && (tp <= 0xD)) ? GBASAVE_FLASH_128K : \
|
||||
(tp == 0xE) ? GBASAVE_SRAM_32K : 0); // last one means invalid
|
||||
|
||||
|
||||
#define GBASAVE_VALID(size) \
|
||||
(((size) == GBASAVE_EEPROM_512) || \
|
||||
((size) == GBASAVE_EEPROM_8K) || \
|
||||
((size) == GBASAVE_SRAM_32K) || \
|
||||
((size) == GBASAVE_FLASH_64K) || \
|
||||
((size) == GBASAVE_FLASH_128K))
|
||||
|
||||
|
||||
// see: http://problemkaputt.de/gbatek.htm#gbacartridgeheader
|
||||
#define AGB_DESTSTR(code) \
|
||||
(((code)[3] == 'J') ? "Japan" : \
|
||||
@ -36,9 +36,9 @@
|
||||
((code)[3] == 'D') ? "German" : \
|
||||
((code)[3] == 'F') ? "French" : \
|
||||
((code)[3] == 'I') ? "Italian" : \
|
||||
((code)[3] == 'S') ? "Spanish" : "Unknown")
|
||||
((code)[3] == 'S') ? "Spanish" : "Unknown")
|
||||
|
||||
|
||||
|
||||
// see: http://3dbrew.org/wiki/3DS_Virtual_Console#Footer
|
||||
// still a lot of unknowns in here, also redundant stuff left out
|
||||
typedef struct {
|
||||
@ -73,7 +73,7 @@ typedef struct {
|
||||
|
||||
// see: http://problemkaputt.de/gbatek.htm#gbacartridgeheader
|
||||
typedef struct {
|
||||
u32 arm7_rom_entry;
|
||||
u32 arm7_rom_entry;
|
||||
u8 logo[0x9C];
|
||||
char game_title[12];
|
||||
char game_code[4];
|
||||
|
@ -113,14 +113,14 @@ int ApplyIPSPatch(const char* patchName, const char* inName, const char* outName
|
||||
int error = IPS_INVALID;
|
||||
UINT outlen_min, outlen_max, outlen_min_mem;
|
||||
snprintf(errName, 256, "%s", patchName);
|
||||
|
||||
|
||||
if (fvx_open(&patchFile, patchName, FA_READ) != FR_OK) return displayError(IPS_INVALID_FILE_PATH);
|
||||
patchSize = fvx_size(&patchFile);
|
||||
ShowProgress(0, patchSize, patchName);
|
||||
|
||||
|
||||
patch = malloc(patchSize);
|
||||
if (!patch || fvx_read(&patchFile, patch, patchSize, NULL) != FR_OK) return displayError(IPS_MEMORY);
|
||||
|
||||
|
||||
// Check validity of patch
|
||||
if (patchSize < 8) return displayError(IPS_INVALID);
|
||||
if (read8() != 'P' ||
|
||||
@ -131,7 +131,7 @@ int ApplyIPSPatch(const char* patchName, const char* inName, const char* outName
|
||||
{
|
||||
return displayError(IPS_INVALID);
|
||||
}
|
||||
|
||||
|
||||
unsigned int offset = read24();
|
||||
unsigned int outlen = 0;
|
||||
unsigned int thisout = 0;
|
||||
@ -144,7 +144,7 @@ int ApplyIPSPatch(const char* patchName, const char* inName, const char* outName
|
||||
ShowProgress(0, patchSize, patchName);
|
||||
ShowProgress(patchOffset, patchSize, patchName);
|
||||
}
|
||||
|
||||
|
||||
unsigned int size = read16();
|
||||
if (size == 0)
|
||||
{
|
||||
@ -180,7 +180,7 @@ int ApplyIPSPatch(const char* patchName, const char* inName, const char* outName
|
||||
outlen_min = outlen;
|
||||
error = IPS_OK;
|
||||
if (w_scrambled) error = IPS_SCRAMBLED;
|
||||
|
||||
|
||||
// start applying patch
|
||||
bool inPlace = false;
|
||||
if (!CheckWritePermissions(outName)) return displayError(IPS_INVALID_FILE_PATH);
|
||||
@ -193,19 +193,19 @@ int ApplyIPSPatch(const char* patchName, const char* inName, const char* outName
|
||||
else if ((fvx_open(&inFile, inName, FA_READ) != FR_OK) ||
|
||||
(fvx_open(&outFile, outName, FA_CREATE_ALWAYS | FA_WRITE | FA_READ) != FR_OK))
|
||||
return displayError(IPS_INVALID_FILE_PATH);
|
||||
|
||||
|
||||
size_t inSize = fvx_size(&inFile);
|
||||
outlen = max(outlen_min, min(inSize, outlen_max));
|
||||
fvx_lseek(&outFile, max(outlen, outlen_min_mem));
|
||||
fvx_lseek(&outFile, 0);
|
||||
size_t outSize = outlen;
|
||||
ShowProgress(0, outSize, outName);
|
||||
|
||||
|
||||
fvx_lseek(&inFile, 0);
|
||||
if (!inPlace && !IPScopy(COPY_IN, min(inSize, outlen), 0)) return displayError(IPS_MEMORY);
|
||||
fvx_lseek(&outFile, inSize);
|
||||
if (outSize > inSize && !IPScopy(COPY_RLE, outSize - inSize, 0)) return displayError(IPS_MEMORY);
|
||||
|
||||
|
||||
fvx_lseek(&patchFile, 5);
|
||||
offset = read24();
|
||||
while (offset != 0x454F46)
|
||||
@ -215,14 +215,14 @@ int ApplyIPSPatch(const char* patchName, const char* inName, const char* outName
|
||||
ShowProgress(0, outSize, outName);
|
||||
ShowProgress(offset, outSize, outName);
|
||||
}
|
||||
|
||||
|
||||
fvx_lseek(&outFile, offset);
|
||||
unsigned int size = read16();
|
||||
if (size == 0 && !IPScopy(COPY_RLE, read16(), read8())) return displayError(IPS_MEMORY);
|
||||
if (size == 0 && !IPScopy(COPY_RLE, read16(), read8())) return displayError(IPS_MEMORY);
|
||||
else if (size != 0 && !IPScopy(COPY_PATCH, size, 0)) return displayError(IPS_MEMORY);
|
||||
offset = read24();
|
||||
}
|
||||
|
||||
|
||||
fvx_lseek(&outFile, outSize);
|
||||
f_truncate(&outFile);
|
||||
return displayError(error);
|
||||
|
@ -11,7 +11,7 @@
|
||||
u32 ValidateNcchHeader(NcchHeader* header) {
|
||||
if (memcmp(header->magic, "NCCH", 4) != 0) // check magic number
|
||||
return 1;
|
||||
|
||||
|
||||
u32 ncch_units = (NCCH_EXTHDR_OFFSET + header->size_exthdr) / NCCH_MEDIA_UNIT; // exthdr
|
||||
if (header->size_plain) { // plain region
|
||||
if (header->offset_plain < ncch_units) return 1; // overlapping plain region
|
||||
@ -26,8 +26,8 @@ u32 ValidateNcchHeader(NcchHeader* header) {
|
||||
ncch_units = (header->offset_romfs + header->size_romfs);
|
||||
}
|
||||
// size check
|
||||
if (ncch_units > header->size) return 1;
|
||||
|
||||
if (ncch_units > header->size) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ u32 GetNcchCtr(u8* ctr, NcchHeader* ncch, u8 section) {
|
||||
if (ncch->version == 1) {
|
||||
memcpy(ctr, &(ncch->partitionId), 8);
|
||||
if (section == 1) { // ExtHeader ctr
|
||||
add_ctr(ctr, NCCH_EXTHDR_OFFSET);
|
||||
add_ctr(ctr, NCCH_EXTHDR_OFFSET);
|
||||
} else if (section == 2) { // ExeFS ctr
|
||||
add_ctr(ctr, ncch->offset_exefs * NCCH_MEDIA_UNIT);
|
||||
} else if (section == 3) { // RomFS ctr
|
||||
@ -47,7 +47,7 @@ u32 GetNcchCtr(u8* ctr, NcchHeader* ncch, u8 section) {
|
||||
ctr[i] = ((u8*) &(ncch->partitionId))[7-i];
|
||||
ctr[8] = section;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -56,25 +56,25 @@ u32 GetNcchSeed(u8* seed, NcchHeader* ncch) {
|
||||
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]);
|
||||
@ -83,17 +83,17 @@ u32 GetNcchSeed(u8* seed, NcchHeader* ncch) {
|
||||
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;
|
||||
@ -106,10 +106,10 @@ u32 GetNcchSeed(u8* seed, NcchHeader* ncch) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// not found -> try seeddb.bin
|
||||
SeedInfo* seeddb = (SeedInfo*) (void*) buffer;
|
||||
size_t len = LoadSupportFile(SEEDDB_NAME, seeddb, STD_BUFFER_SIZE);
|
||||
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)
|
||||
@ -123,7 +123,7 @@ u32 GetNcchSeed(u8* seed, NcchHeader* ncch) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// out of options -> failed!
|
||||
free(buffer);
|
||||
return 1;
|
||||
@ -150,10 +150,10 @@ u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid) {
|
||||
u8 flags7 = crypto & 0xFF;
|
||||
u32 keyslot = (!keyid || !flags3) ? 0x2C : // standard / secure3 / secure4 / 7.x crypto
|
||||
(flags3 == 0x0A) ? 0x18 : (flags3 == 0x0B) ? 0x1B : 0x25;
|
||||
|
||||
|
||||
if (flags7 & 0x04)
|
||||
return 1;
|
||||
|
||||
|
||||
if (flags7 & 0x01) { // fixed key crypto
|
||||
// from https://github.com/profi200/Project_CTR/blob/master/makerom/pki/dev.h
|
||||
u8 zeroKey[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
@ -164,11 +164,11 @@ u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid) {
|
||||
use_aeskey(0x11);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// load key X from file if required
|
||||
if ((keyslot != 0x2C) && (LoadKeyFromFile(NULL, keyslot, 'X', NULL) != 0))
|
||||
return 1;
|
||||
|
||||
|
||||
// key Y for seed and non seed
|
||||
if (keyid && (flags7 & 0x20)) { // seed crypto
|
||||
static u8 seedkeyY[16+16] __attribute__((aligned(32))) = { 0 };
|
||||
@ -188,12 +188,12 @@ u32 SetNcchKey(NcchHeader* ncch, u16 crypto, u32 keyid) {
|
||||
setup_aeskeyY(keyslot, ncch->signature);
|
||||
}
|
||||
use_aeskey(keyslot);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// this is used to force and check crypto setup
|
||||
// (also prevents SHA register usage later on)
|
||||
// (also prevents SHA register usage later on)
|
||||
u32 SetupNcchCrypto(NcchHeader* ncch, u16 crypt_to) {
|
||||
u16 crypt_from = NCCH_GET_CRYPTO(ncch);
|
||||
u32 res_from = ((crypt_from & NCCH_NOCRYPTO) ||
|
||||
@ -207,14 +207,14 @@ u32 CryptNcchSection(void* data, u32 offset_data, u32 size_data, u32 offset_sect
|
||||
u32 offset_ctr, NcchHeader* ncch, u32 snum, u16 crypt_to, u32 keyid) {
|
||||
u16 crypt_from = NCCH_GET_CRYPTO(ncch);
|
||||
const u32 mode = AES_CNT_CTRNAND_MODE;
|
||||
|
||||
|
||||
// check if section in data
|
||||
if ((offset_section >= offset_data + size_data) ||
|
||||
(offset_data >= offset_section + size_section) ||
|
||||
!size_section) {
|
||||
return 0; // section not in data
|
||||
}
|
||||
|
||||
|
||||
// determine data / offset / size
|
||||
u8* data8 = (u8*)data;
|
||||
u8* data_i = data8;
|
||||
@ -226,7 +226,7 @@ u32 CryptNcchSection(void* data, u32 offset_data, u32 size_data, u32 offset_sect
|
||||
size_i = size_section - offset_i;
|
||||
if (size_i > size_data - (data_i - data8))
|
||||
size_i = size_data - (data_i - data8);
|
||||
|
||||
|
||||
// actual decryption stuff
|
||||
u8 ctr[16];
|
||||
GetNcchCtr(ctr, ncch, snum);
|
||||
@ -238,7 +238,7 @@ u32 CryptNcchSection(void* data, u32 offset_data, u32 size_data, u32 offset_sect
|
||||
if (SetNcchKey(ncch, crypt_to, keyid) != 0) return 1;
|
||||
ctr_decrypt_byte(data_i, data_i, size_i, offset_i + offset_ctr, mode, ctr);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -247,11 +247,11 @@ u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* e
|
||||
const u32 offset_flag3 = 0x188 + 3;
|
||||
const u32 offset_flag7 = 0x188 + 7;
|
||||
u16 crypt_from = NCCH_GET_CRYPTO(ncch);
|
||||
|
||||
|
||||
// check for encryption
|
||||
if ((crypt_to & crypt_from & NCCH_NOCRYPTO) || (crypt_to == crypt_from))
|
||||
return 0; // desired end result already met
|
||||
|
||||
|
||||
// ncch flags handling
|
||||
if ((offset <= offset_flag3) && (offset + size > offset_flag3))
|
||||
((u8*)data)[offset_flag3 - offset] = (crypt_to >> 8);
|
||||
@ -259,7 +259,7 @@ u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* e
|
||||
((u8*)data)[offset_flag7 - offset] &= ~(0x01|0x20|0x04);
|
||||
((u8*)data)[offset_flag7 - offset] |= (crypt_to & (0x01|0x20|0x04));
|
||||
}
|
||||
|
||||
|
||||
// exthdr handling
|
||||
if (ncch->size_exthdr) {
|
||||
if (CryptNcchSection(data, offset, size,
|
||||
@ -267,14 +267,14 @@ u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* e
|
||||
NCCH_EXTHDR_SIZE,
|
||||
0, ncch, 1, crypt_to, 0) != 0) return 1;
|
||||
}
|
||||
|
||||
|
||||
// exefs handling
|
||||
if (ncch->size_exefs) {
|
||||
// exefs header handling
|
||||
if (CryptNcchSection(data, offset, size,
|
||||
ncch->offset_exefs * NCCH_MEDIA_UNIT,
|
||||
0x200, 0, ncch, 2, crypt_to, 0) != 0) return 1;
|
||||
|
||||
|
||||
// exefs file handling
|
||||
if (exefs) for (u32 i = 0; i < 10; i++) {
|
||||
ExeFsFileHeader* file = exefs->files + i;
|
||||
@ -291,7 +291,7 @@ u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* e
|
||||
ncch, 2, crypt_to, 0) != 0) return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// romfs handling
|
||||
if (ncch->size_romfs) {
|
||||
if (CryptNcchSection(data, offset, size,
|
||||
@ -299,7 +299,7 @@ u32 CryptNcch(void* data, u32 offset, u32 size, NcchHeader* ncch, ExeFsHeader* e
|
||||
ncch->size_romfs * NCCH_MEDIA_UNIT,
|
||||
0, ncch, 3, crypt_to, 1) != 0) return 1;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -311,31 +311,31 @@ u32 CryptNcchSequential(void* data, u32 offset, u32 size, u16 crypt_to) {
|
||||
static ExeFsHeader exefs = { 0 };
|
||||
static NcchHeader* ncchptr = NULL;
|
||||
static ExeFsHeader* exefsptr = NULL;
|
||||
|
||||
|
||||
// fetch ncch header from data
|
||||
if ((offset == 0) && (size >= sizeof(NcchHeader))) {
|
||||
memcpy(&ncch, data, sizeof(NcchHeader));
|
||||
ncchptr = (ValidateNcchHeader(&ncch) == 0) ? &ncch : NULL;
|
||||
exefsptr = NULL;
|
||||
}
|
||||
|
||||
|
||||
// safety check, ncch pointer
|
||||
if (!ncchptr) return 1;
|
||||
|
||||
|
||||
// fetch exefs header from data
|
||||
if (ncchptr->offset_exefs && !exefsptr) {
|
||||
u32 offset_exefs = ncchptr->offset_exefs * NCCH_MEDIA_UNIT;
|
||||
if ((offset <= offset_exefs) &&
|
||||
((offset + size) >= offset_exefs + sizeof(ExeFsHeader))) {
|
||||
memcpy(&exefs, (u8*)data + offset_exefs - offset, sizeof(ExeFsHeader));
|
||||
if ((NCCH_ENCRYPTED(ncchptr)) &&
|
||||
if ((NCCH_ENCRYPTED(ncchptr)) &&
|
||||
(DecryptNcch((u8*) &exefs, offset_exefs, sizeof(ExeFsHeader), ncchptr, NULL) != 0))
|
||||
return 1;
|
||||
if (ValidateExeFsHeader(&exefs, 0) != 0) return 1;
|
||||
exefsptr = &exefs;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return CryptNcch(data, offset, size, ncchptr, exefsptr, crypt_to);
|
||||
}
|
||||
|
||||
@ -343,17 +343,17 @@ u32 SetNcchSdFlag(void* data) { // data must be at least 0x600 byte and start wi
|
||||
NcchHeader* ncch = (NcchHeader*) data;
|
||||
NcchExtHeader* exthdr = (NcchExtHeader*) (void*) ((u8*)data + NCCH_EXTHDR_OFFSET);
|
||||
NcchExtHeader exthdr_dec;
|
||||
|
||||
|
||||
if ((ValidateNcchHeader(ncch) != 0) || (!ncch->size_exthdr))
|
||||
return 0; // no extheader
|
||||
memcpy(&exthdr_dec, exthdr, sizeof(NcchExtHeader));
|
||||
if (DecryptNcch((u8*) &exthdr_dec, NCCH_EXTHDR_OFFSET, sizeof(NcchExtHeader), ncch, NULL) != 0)
|
||||
return 1;
|
||||
if (exthdr_dec.flag & (1<<1)) return 0; // flag already set
|
||||
|
||||
|
||||
exthdr_dec.flag |= (1<<1);
|
||||
exthdr->flag ^= (1<<1);
|
||||
sha_quick(ncch->hash_exthdr, &exthdr_dec, 0x400, SHA256_MODE);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -18,25 +18,25 @@ u32 FixNcchInfoEntry(NcchInfoEntry* entry, u32 version) {
|
||||
} else if (version != 4) { // !ncchinfo v4.0/v4.1/v4.2
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// poor man's UTF-16 -> UTF-8
|
||||
if (entry->filename[1] == 0x00) {
|
||||
for (u32 i = 1; i < (sizeof(entry->filename) / 2); i++)
|
||||
entry->filename[i] = entry->filename[i*2];
|
||||
}
|
||||
|
||||
|
||||
// fix sdmc: prefix
|
||||
if (memcmp(entry->filename, "sdmc:", 5) == 0)
|
||||
memmove(entry->filename, entry->filename + 5, 112 - 5);
|
||||
|
||||
|
||||
// workaround (1) for older (v4.0) ncchinfo.bin
|
||||
// this combination means seed crypto rather than FixedKey
|
||||
if ((entry->ncchFlag7 == 0x01) && entry->ncchFlag3)
|
||||
entry->ncchFlag7 = 0x20;
|
||||
|
||||
|
||||
// workaround (2) for older (v4.1) ncchinfo.bin
|
||||
if (!entry->size_b) entry->size_b = entry->size_mb * 1024 * 1024;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -50,10 +50,10 @@ u32 BuildNcchInfoXorpad(void* buffer, NcchInfoEntry* entry, u32 size, u32 offset
|
||||
ncch.programId = ncch.partitionId = entry->titleId;
|
||||
if (SetNcchKey(&ncch, NCCH_GET_CRYPTO(&ncch), 1) != 0)
|
||||
return 1;
|
||||
|
||||
|
||||
// write xorpad
|
||||
memset(buffer, 0, size);
|
||||
ctr_decrypt_byte(buffer, buffer, size, offset, AES_CNT_CTRNAND_MODE, entry->ctr);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ u32 ValidateNcsdHeader(NcsdHeader* header) {
|
||||
if ((memcmp(header->magic, "NCSD", 4) != 0) || // check magic number
|
||||
(memcmp(header->partitions_fs_type, zeroes, 8) != 0) || !header->mediaId) // prevent detection of NAND images
|
||||
return 1;
|
||||
|
||||
|
||||
u32 data_units = 0;
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
NcchPartition* partition = header->partitions + i;
|
||||
@ -18,7 +18,7 @@ u32 ValidateNcsdHeader(NcsdHeader* header) {
|
||||
}
|
||||
if (data_units > header->size)
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ u64 GetNcsdTrimmedSize(NcsdHeader* header) {
|
||||
if (!partition->size) continue;
|
||||
data_units = (partition_end > data_units) ? partition_end : data_units;
|
||||
}
|
||||
|
||||
|
||||
return data_units * NCSD_MEDIA_UNIT;
|
||||
}
|
||||
|
||||
@ -38,11 +38,11 @@ u64 GetNcsdTrimmedSize(NcsdHeader* header) {
|
||||
u32 CryptNcsdSequential(void* data, u32 offset_data, u32 size_data, u16 crypto) {
|
||||
// warning: this will only work for sequential processing
|
||||
static NcsdHeader ncsd;
|
||||
|
||||
|
||||
// fetch ncsd header from data
|
||||
if ((offset_data == 0) && (size_data >= sizeof(NcsdHeader)))
|
||||
memcpy(&ncsd, data, sizeof(NcsdHeader));
|
||||
|
||||
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
NcchPartition* partition = ncsd.partitions + i;
|
||||
u32 offset_p = partition->offset * NCSD_MEDIA_UNIT;
|
||||
@ -68,7 +68,7 @@ u32 CryptNcsdSequential(void* data, u32 offset_data, u32 size_data, u16 crypto)
|
||||
if (CryptNcchSequential(data_i, offset_i, size_i, crypto) != 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ u32 BuildTwlSaveHeader(void* sav, u32 size) {
|
||||
if (size / (u32) sct_size > 0xFFFF)
|
||||
return 1;
|
||||
|
||||
// fit max number of sectors into size
|
||||
// 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;
|
||||
@ -143,15 +143,15 @@ u32 FindNitroRomDir(u32 dirid, u32* fileid, u8** fnt_entry, TwlHeader* hdr, u8*
|
||||
NitroFntBaseEntry* fnt_base = (NitroFntBaseEntry*) fnt;
|
||||
NitroFntBaseEntry* fnt_dir = &((NitroFntBaseEntry*) fnt)[dirid];
|
||||
NitroFatEntry* fat_lut = (NitroFatEntry*) fat;
|
||||
|
||||
|
||||
// base sanity checks
|
||||
if (fnt_base->parent_id*sizeof(NitroFntBaseEntry) > fnt_base->subtable_offset) return 1; // invalid FNT
|
||||
if (dirid >= fnt_base->parent_id) return 1; // dir ID out of bounds
|
||||
|
||||
|
||||
// set first FNT entry / fileid
|
||||
*fnt_entry = fnt + fnt_dir->subtable_offset;
|
||||
*fileid = fnt_dir->file0_id;
|
||||
|
||||
|
||||
// check subtable / directory validity
|
||||
u32 fid = *fileid;
|
||||
for (u8* entry = *fnt_entry;; entry = FNT_ENTRY_NEXT(entry)) {
|
||||
@ -161,29 +161,29 @@ u32 FindNitroRomDir(u32 dirid, u32* fileid, u8** fnt_entry, TwlHeader* hdr, u8*
|
||||
if (!FNT_ENTRY_ISDIR(entry)) fid++;
|
||||
}
|
||||
if (fid*sizeof(NitroFatEntry) > hdr->fat_size) return 1; // corrupt fnt / fat
|
||||
|
||||
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 NextNitroRomEntry(u32* fileid, u8** fnt_entry) {
|
||||
// check for end of subtable
|
||||
if (!*fnt_entry || !**fnt_entry) return 1;
|
||||
|
||||
|
||||
// advance to next entry
|
||||
if (!FNT_ENTRY_ISDIR(*fnt_entry)) (*fileid)++;
|
||||
*fnt_entry += FNT_ENTRY_LEN(*fnt_entry);
|
||||
|
||||
|
||||
// check for end of subtable
|
||||
if (!**fnt_entry) return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 ReadNitroRomEntry(u64* offset, u64* size, bool* is_dir, u32 fileid, u8* fnt_entry, u8* fat) {
|
||||
// check for end of subtable
|
||||
if (!fnt_entry || !*fnt_entry) return 1;
|
||||
|
||||
|
||||
// decipher FNT entry
|
||||
*is_dir = FNT_ENTRY_ISDIR(fnt_entry);
|
||||
if (!(*is_dir)) { // for files
|
||||
@ -195,6 +195,6 @@ u32 ReadNitroRomEntry(u64* offset, u64* size, bool* is_dir, u32 fileid, u8* fnt_
|
||||
*offset = (u64) (fnt_entry[1+fnlen]|(fnt_entry[1+fnlen+1]<<8)) & 0xFFF; // dir ID goes in offset
|
||||
*size = 0;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
// see: http://problemkaputt.de/gbatek.htm#dscartridgeicontitle
|
||||
// v0x0001 -> 0x0840 byte (contains JPN, USA, FRE, GER, ITA, ESP titles)
|
||||
// v0x0002 -> 0x0940 byte (adds CHN title)
|
||||
// v0x0003 -> 0x0A40 byte (adds KOR title)
|
||||
// v0x0003 -> 0x0A40 byte (adds KOR title)
|
||||
// v0x0103 -> 0x23C0 byte (adds TWL animated icon data)
|
||||
#define TWLICON_SIZE_DATA(v) ((v == 0x0001) ? 0x0840 : (v == 0x0002) ? 0x0940 : \
|
||||
(v == 0x0003) ? 0x1240 : (v == 0x0103) ? 0x23C0 : 0x0000)
|
||||
|
@ -58,12 +58,12 @@ u32 BuildLv3Index(RomFsLv3Index* index, u8* lv3) {
|
||||
index->filehash = (u32*) (void*) (lv3 + hdr->offset_filehash);
|
||||
index->filemeta = lv3 + hdr->offset_filemeta;
|
||||
index->filedata = NULL;
|
||||
|
||||
|
||||
index->mod_dir = (hdr->size_dirhash / sizeof(u32));
|
||||
index->mod_file = (hdr->size_filehash / sizeof(u32));
|
||||
index->size_dirmeta = hdr->size_dirmeta;
|
||||
index->size_filemeta = hdr->size_filemeta;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -77,13 +77,13 @@ u32 HashLv3Path(u16* wname, u32 name_len, u32 offset_parent) {
|
||||
|
||||
RomFsLv3DirMeta* GetLv3DirMeta(const char* name, u32 offset_parent, RomFsLv3Index* index) {
|
||||
RomFsLv3DirMeta* meta;
|
||||
|
||||
|
||||
// wide (UTF-16) name
|
||||
u16 wname[256];
|
||||
int name_len = utf8_to_utf16(wname, (u8*) name, 255, 255);
|
||||
if (name_len <= 0) return NULL;
|
||||
wname[name_len] = 0;
|
||||
|
||||
|
||||
// hashing, first offset
|
||||
u32 hash = HashLv3Path(wname, name_len, offset_parent);
|
||||
u32 offset = index->dirhash[hash % index->mod_dir];
|
||||
@ -96,19 +96,19 @@ RomFsLv3DirMeta* GetLv3DirMeta(const char* name, u32 offset_parent, RomFsLv3Inde
|
||||
(memcmp(wname, meta->wname, name_len * 2) == 0))
|
||||
break;
|
||||
}
|
||||
|
||||
return (offset >= index->size_dirmeta) ? NULL : meta;
|
||||
|
||||
return (offset >= index->size_dirmeta) ? NULL : meta;
|
||||
}
|
||||
|
||||
RomFsLv3FileMeta* GetLv3FileMeta(const char* name, u32 offset_parent, RomFsLv3Index* index) {
|
||||
RomFsLv3FileMeta* meta;
|
||||
|
||||
|
||||
// wide (UTF-16) name
|
||||
u16 wname[256];
|
||||
int name_len = utf8_to_utf16(wname, (u8*) name, 255, 255);
|
||||
if (name_len <= 0) return NULL;
|
||||
wname[name_len] = 0;
|
||||
|
||||
|
||||
// hashing, first offset
|
||||
u32 hash = HashLv3Path(wname, name_len, offset_parent);
|
||||
u32 offset = index->filehash[hash % index->mod_file];
|
||||
@ -121,6 +121,6 @@ RomFsLv3FileMeta* GetLv3FileMeta(const char* name, u32 offset_parent, RomFsLv3In
|
||||
(memcmp(wname, meta->wname, name_len * 2) == 0))
|
||||
break;
|
||||
}
|
||||
|
||||
return (offset >= index->size_filemeta) ? NULL : meta;
|
||||
|
||||
return (offset >= index->size_filemeta) ? NULL : meta;
|
||||
}
|
||||
|
@ -4,14 +4,14 @@
|
||||
u32 BuildTadContentTable(void* table, void* header) {
|
||||
TadHeader* hdr = (TadHeader*) header;
|
||||
TadContentTable* tbl = (TadContentTable*) table;
|
||||
|
||||
|
||||
if (strncmp(hdr->magic, TAD_HEADER_MAGIC, strlen(TAD_HEADER_MAGIC)) != 0)
|
||||
return 1;
|
||||
|
||||
|
||||
tbl->banner_end = 0 + sizeof(TadBanner) + sizeof(TadBlockMetaData);
|
||||
tbl->header_end = tbl->banner_end + sizeof(TadHeader) + sizeof(TadBlockMetaData);
|
||||
tbl->footer_end = tbl->header_end + sizeof(TadFooter) + sizeof(TadBlockMetaData);
|
||||
|
||||
|
||||
u32 content_end_last = tbl->footer_end;
|
||||
for (u32 i = 0; i < TAD_NUM_CONTENT; i++) {
|
||||
tbl->content_end[i] = content_end_last;
|
||||
@ -19,6 +19,6 @@ u32 BuildTadContentTable(void* table, void* header) {
|
||||
tbl->content_end[i] += align(hdr->content_size[i], 0x10) + sizeof(TadBlockMetaData);
|
||||
content_end_last = tbl->content_end[i];
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -23,18 +23,18 @@ u32 ValidateTicketSignature(Ticket* ticket) {
|
||||
static bool got_modexp = false;
|
||||
static u32 mod[0x100 / 0x4] = { 0 };
|
||||
static u32 exp = 0;
|
||||
|
||||
|
||||
if (!got_modexp) {
|
||||
// grab mod/exp from cert from cert.db
|
||||
if (LoadCertFromCertDb(0x3F10, NULL, mod, &exp) == 0)
|
||||
got_modexp = true;
|
||||
else return 1;
|
||||
}
|
||||
|
||||
|
||||
if (!RSA_setKey2048(3, mod, exp) ||
|
||||
!RSA_verify2048((void*) &(ticket->signature), (void*) &(ticket->issuer), GetTicketSize(ticket) - 0x140))
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ u32 BuildFakeTicket(Ticket* ticket, u8* title_id) {
|
||||
ticket->audit = 0x01; // whatever
|
||||
memcpy(ticket->content_index, ticket_cnt_index, sizeof(ticket_cnt_index));
|
||||
memset(&ticket->content_index[sizeof(ticket_cnt_index)], 0xFF, 0x80); // 1024 content indexes
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -73,14 +73,14 @@ u32 GetTicketSize(const Ticket* ticket) {
|
||||
|
||||
u32 BuildTicketCert(u8* tickcert) {
|
||||
static const u8 cert_hash_expected[0x20] = {
|
||||
0xDC, 0x15, 0x3C, 0x2B, 0x8A, 0x0A, 0xC8, 0x74, 0xA9, 0xDC, 0x78, 0x61, 0x0E, 0x6A, 0x8F, 0xE3,
|
||||
0xDC, 0x15, 0x3C, 0x2B, 0x8A, 0x0A, 0xC8, 0x74, 0xA9, 0xDC, 0x78, 0x61, 0x0E, 0x6A, 0x8F, 0xE3,
|
||||
0xE6, 0xB1, 0x34, 0xD5, 0x52, 0x88, 0x73, 0xC9, 0x61, 0xFB, 0xC7, 0x95, 0xCB, 0x47, 0xE6, 0x97
|
||||
};
|
||||
static const u8 cert_hash_expected_dev[0x20] = {
|
||||
0x97, 0x2A, 0x32, 0xFF, 0x9D, 0x4B, 0xAA, 0x2F, 0x1A, 0x24, 0xCF, 0x21, 0x13, 0x87, 0xF5, 0x38,
|
||||
0xC6, 0x4B, 0xD4, 0x8F, 0xDF, 0x13, 0x21, 0x3D, 0xFC, 0x72, 0xFC, 0x8D, 0x9F, 0xDD, 0x01, 0x0E
|
||||
};
|
||||
|
||||
|
||||
// open certs.db file on SysNAND
|
||||
FIL db;
|
||||
UINT bytes_read;
|
||||
@ -94,13 +94,13 @@ u32 BuildTicketCert(u8* tickcert) {
|
||||
f_lseek(&db, 0x3A00);
|
||||
f_read(&db, tickcert + 0x4F0, 0x210, &bytes_read);
|
||||
f_close(&db);
|
||||
|
||||
|
||||
// check the certificate hash
|
||||
u8 cert_hash[0x20];
|
||||
sha_quick(cert_hash, tickcert, TICKET_CDNCERT_SIZE, SHA256_MODE);
|
||||
if (memcmp(cert_hash, IS_DEVKIT ? cert_hash_expected_dev : cert_hash_expected, 0x20) != 0)
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -32,10 +32,10 @@ u32 CryptTitleKey(TitleKeyEntry* tik, bool encrypt, bool devkit) {
|
||||
{0x75, 0x05, 0x52, 0xBF, 0xAA, 0x1C, 0x04, 0x07, 0x55, 0xC8, 0xD5, 0x9A, 0x55, 0xF9, 0xAD, 0x1F} , // 4
|
||||
{0xAA, 0xDA, 0x4C, 0xA8, 0xF6, 0xE5, 0xA9, 0x77, 0xE0, 0xA0, 0xF9, 0xE4, 0x76, 0xCF, 0x0D, 0x63} , // 5
|
||||
};
|
||||
|
||||
|
||||
u32 mode = (encrypt) ? AES_CNT_TITLEKEY_ENCRYPT_MODE : AES_CNT_TITLEKEY_DECRYPT_MODE;
|
||||
u8 ctr[16] = { 0 };
|
||||
|
||||
|
||||
// setup key 0x3D // ctr
|
||||
if (tik->commonkey_idx >= 6) return 1;
|
||||
if (!devkit) setup_aeskeyY(0x3D, (void*) common_keyy[tik->commonkey_idx]);
|
||||
@ -43,7 +43,7 @@ u32 CryptTitleKey(TitleKeyEntry* tik, bool encrypt, bool devkit) {
|
||||
use_aeskey(0x3D);
|
||||
memcpy(ctr, tik->title_id, 8);
|
||||
set_ctr(ctr);
|
||||
|
||||
|
||||
// decrypt / encrypt the titlekey
|
||||
aes_decrypt(tik->titlekey, tik->titlekey, 1, mode);
|
||||
return 0;
|
||||
@ -54,7 +54,7 @@ u32 GetTitleKey(u8* titlekey, Ticket* ticket) {
|
||||
memcpy(tik.title_id, ticket->title_id, 8);
|
||||
memcpy(tik.titlekey, ticket->titlekey, 16);
|
||||
tik.commonkey_idx = ticket->commonkey_idx;
|
||||
|
||||
|
||||
if (CryptTitleKey(&tik, false, TICKET_DEVKIT(ticket)) != 0) return 1;
|
||||
memcpy(titlekey, tik.titlekey, 16);
|
||||
return 0;
|
||||
@ -64,7 +64,7 @@ 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;
|
||||
|
||||
|
||||
strncpy(path_store, GetMountPath(), 256);
|
||||
if (*path_store) path_bak = path_store;
|
||||
if (!InitImgFS(path_db))
|
||||
@ -81,37 +81,37 @@ u32 FindTicket(Ticket** ticket, u8* title_id, bool force_legit, bool emunand) {
|
||||
|
||||
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(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(path_bak);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
if ((fvx_qread(tik_path, *ticket, 0, size, NULL) != FR_OK) ||
|
||||
(force_legit && (ValidateTicketSignature(*ticket) != 0))) {
|
||||
free(*ticket);
|
||||
InitImgFS(path_bak);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
InitImgFS(path_bak);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fvx_closedir(&dir);
|
||||
}
|
||||
|
||||
|
||||
InitImgFS(path_bak);
|
||||
return 1;
|
||||
}
|
||||
@ -120,12 +120,12 @@ u32 FindTitleKey(Ticket* ticket, u8* title_id) {
|
||||
bool found = false;
|
||||
TitleKeysInfo* tikdb = (TitleKeysInfo*) malloc(STD_BUFFER_SIZE); // more than enough
|
||||
if (!tikdb) return 1;
|
||||
|
||||
|
||||
// search for a titlekey inside encTitleKeys.bin / decTitleKeys.bin
|
||||
// when found, add it to the ticket
|
||||
for (u32 enc = 0; (enc <= 1) && !found; enc++) {
|
||||
u32 len = LoadSupportFile((enc) ? TIKDB_NAME_ENC : TIKDB_NAME_DEC, tikdb, STD_BUFFER_SIZE);
|
||||
|
||||
|
||||
if (len == 0) continue; // file not found
|
||||
if (tikdb->n_entries > (len - 16) / 32)
|
||||
continue; // filesize / titlekey db size mismatch
|
||||
@ -141,7 +141,7 @@ u32 FindTitleKey(Ticket* ticket, u8* title_id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
free(tikdb);
|
||||
return (found) ? 0 : 1;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ u32 BuildTitleInfoEntryTmd(TitleInfoEntry* tie, TitleMetaData* tmd, bool sd) {
|
||||
u64 title_id = getbe64(tmd->title_id);
|
||||
bool has_idx1 = false;
|
||||
bool has_idx2 = false;
|
||||
|
||||
|
||||
// set basic values
|
||||
memset(tie, 0x00, sizeof(TitleInfoEntry));
|
||||
tie->title_type = 0x40;
|
||||
@ -34,7 +34,7 @@ u32 BuildTitleInfoEntryTmd(TitleInfoEntry* tie, TitleMetaData* tmd, bool sd) {
|
||||
((tmd->twl_flag & 0x2) ? align(sizeof(TwlIconData), align_size) : 0);
|
||||
}
|
||||
|
||||
// contents title size + some additional stuff
|
||||
// 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++) {
|
||||
@ -55,7 +55,7 @@ u32 BuildTitleInfoEntryTmd(TitleInfoEntry* tie, TitleMetaData* tmd, bool sd) {
|
||||
|
||||
u32 BuildTitleInfoEntryTwl(TitleInfoEntry* tie, TitleMetaData* tmd, TwlHeader* twl) {
|
||||
u64 title_id = getbe64(tmd->title_id);
|
||||
|
||||
|
||||
// build the basic titledb entry
|
||||
if (BuildTitleInfoEntryTmd(tie, tmd, false) != 0) return 1;
|
||||
|
||||
@ -90,7 +90,7 @@ u32 BuildTitleInfoEntryNcch(TitleInfoEntry* tie, TitleMetaData* tmd, NcchHeader*
|
||||
|
||||
// NCCH titles need no content0 ID
|
||||
tie->content0_id = 0;
|
||||
|
||||
|
||||
// specific flags
|
||||
// see: http://3dbrew.org/wiki/Titles
|
||||
if (!((title_id >> 32) & 0x10)) // not a system title
|
||||
|
@ -18,18 +18,18 @@ u32 ValidateTmdSignature(TitleMetaData* tmd) {
|
||||
static bool got_modexp = false;
|
||||
static u32 mod[0x100 / 4] = { 0 };
|
||||
static u32 exp = 0;
|
||||
|
||||
|
||||
if (!got_modexp) {
|
||||
// grab mod/exp from cert from cert.db
|
||||
if (LoadCertFromCertDb(0x3C10, NULL, mod, &exp) == 0)
|
||||
got_modexp = true;
|
||||
else return 1;
|
||||
}
|
||||
|
||||
|
||||
if (!RSA_setKey2048(3, mod, exp) ||
|
||||
!RSA_verify2048((void*) &(tmd->signature), (void*) &(tmd->issuer), 0xC4))
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -98,20 +98,20 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size
|
||||
memcpy(tmd->contentinfo[0].cmd_count, tmd->content_count, 2);
|
||||
memset(tmd->contentinfo[0].hash, 0xFF, 0x20); // placeholder (hash)
|
||||
// nothing to do for content list (yet)
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 BuildTmdCert(u8* tmdcert) {
|
||||
static const u8 cert_hash_expected[0x20] = {
|
||||
0x91, 0x5F, 0x77, 0x3A, 0x07, 0x82, 0xD4, 0x27, 0xC4, 0xCE, 0xF5, 0x49, 0x25, 0x33, 0xE8, 0xEC,
|
||||
0x91, 0x5F, 0x77, 0x3A, 0x07, 0x82, 0xD4, 0x27, 0xC4, 0xCE, 0xF5, 0x49, 0x25, 0x33, 0xE8, 0xEC,
|
||||
0xF6, 0xFE, 0xA1, 0xEB, 0x8C, 0xCF, 0x59, 0x6E, 0x69, 0xBA, 0x2A, 0x38, 0x8D, 0x73, 0x8A, 0xE1
|
||||
};
|
||||
static const u8 cert_hash_expected_dev[0x20] = {
|
||||
0x49, 0xC9, 0x41, 0x56, 0xCA, 0x86, 0xBD, 0x1F, 0x36, 0x51, 0x51, 0x6A, 0x4A, 0x9F, 0x54, 0xA1,
|
||||
0xC2, 0xE9, 0xCA, 0x93, 0x94, 0xF4, 0x29, 0xA0, 0x38, 0x54, 0x75, 0xFF, 0xAB, 0x6E, 0x8E, 0x71
|
||||
};
|
||||
|
||||
|
||||
// open certs.db file on SysNAND
|
||||
FIL db;
|
||||
UINT bytes_read;
|
||||
@ -125,12 +125,12 @@ u32 BuildTmdCert(u8* tmdcert) {
|
||||
f_lseek(&db, 0x3A00);
|
||||
f_read(&db, tmdcert + 0x4F0, 0x210, &bytes_read);
|
||||
f_close(&db);
|
||||
|
||||
|
||||
// check the certificate hash
|
||||
u8 cert_hash[0x20];
|
||||
sha_quick(cert_hash, tmdcert, TMD_CDNCERT_SIZE, SHA256_MODE);
|
||||
if (memcmp(cert_hash, IS_DEVKIT ? cert_hash_expected_dev : cert_hash_expected, 0x20) != 0)
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
||||
#define SPI_512B_EEPROM_CMD_RDLO 3
|
||||
#define SPI_512B_EEPROM_CMD_RDHI 11
|
||||
|
||||
#define SPI_EEPROM_CMD_WRITE 2
|
||||
#define SPI_EEPROM_CMD_WRITE 2
|
||||
|
||||
#define SPI_CMD_READ 3
|
||||
|
||||
@ -119,7 +119,7 @@ int CardSPIWriteRead(CardSPIType type, const void* cmd, u32 cmdSize, void* answe
|
||||
SPI_DoXfer(SPI_DEV_CART_FLASH, transfers, 3, true);
|
||||
|
||||
REG_CFG9_CARDCTL &= ~CARDCTL_SPICARD;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -148,12 +148,12 @@ int CardSPIEnableWriting_regular(CardSPIType type) {
|
||||
|
||||
if (res) return res;
|
||||
cmd = SPI_CMD_RDSR;
|
||||
|
||||
|
||||
do {
|
||||
res = CardSPIWriteRead(type, &cmd, 1, &statusReg, 1, 0, 0);
|
||||
if (res) return res;
|
||||
} while(statusReg & ~SPI_FLG_WEL);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -176,17 +176,17 @@ int CardSPIReadJEDECIDAndStatusReg(CardSPIType type, u32* id, u8* statusReg) {
|
||||
u32 id_ = 0;
|
||||
int res = CardSPIWaitWriteEnd(type, 0);
|
||||
if (res) return res;
|
||||
|
||||
|
||||
if ((res = CardSPIWriteRead(type, &cmd, 1, idbuf, 3, 0, 0))) return res;
|
||||
|
||||
|
||||
id_ = (idbuf[0] << 16) | (idbuf[1] << 8) | idbuf[2];
|
||||
cmd = SPI_CMD_RDSR;
|
||||
|
||||
|
||||
if ((res = CardSPIWriteRead(type, &cmd, 1, ®, 1, 0, 0))) return res;
|
||||
|
||||
|
||||
if (id) *id = id_;
|
||||
if (statusReg) *statusReg = reg;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -207,19 +207,19 @@ u32 CardSPIGetCapacity(CardSPIType type) {
|
||||
|
||||
int CardSPIWriteSaveData_9bit(CardSPIType type, u32 offset, const void* data, u32 size) {
|
||||
u8 cmd[2] = { (offset >= 0x100) ? SPI_512B_EEPROM_CMD_WRHI : SPI_512B_EEPROM_CMD_WRLO, (u8) offset };
|
||||
|
||||
|
||||
return _SPIWriteTransaction(type, cmd, 2, (void*) ((u8*) data), size);
|
||||
}
|
||||
|
||||
int CardSPIWriteSaveData_16bit(CardSPIType type, u32 offset, const void* data, u32 size) {
|
||||
u8 cmd[3] = { type.chip->writeCommand, (u8)(offset >> 8), (u8) offset };
|
||||
|
||||
|
||||
return _SPIWriteTransaction(type, cmd, 3, (void*) ((u8*) data), size);
|
||||
}
|
||||
|
||||
int CardSPIWriteSaveData_24bit_write(CardSPIType type, u32 offset, const void* data, u32 size) {
|
||||
u8 cmd[4] = { type.chip->writeCommand, (u8)(offset >> 16), (u8)(offset >> 8), (u8) offset };
|
||||
|
||||
|
||||
return _SPIWriteTransaction(type, cmd, 4, (void*) ((u8*) data), size);
|
||||
}
|
||||
|
||||
@ -270,60 +270,60 @@ int CardSPIWriteSaveData_24bit_erase_program(CardSPIType type, u32 offset, const
|
||||
|
||||
int CardSPIWriteSaveData(CardSPIType type, u32 offset, const void* data, u32 size) {
|
||||
if (type.chip == NO_CHIP) return 1;
|
||||
|
||||
|
||||
if (size == 0) return 0;
|
||||
size = min(size, CardSPIGetCapacity(type) - offset);
|
||||
u32 end = offset + size;
|
||||
u32 pos = offset;
|
||||
u32 writeSize = type.chip->writeSize;
|
||||
if (writeSize == 0) return 0xC8E13404;
|
||||
|
||||
|
||||
int res = CardSPIWaitWriteEnd(type, 1000);
|
||||
if (res) return res;
|
||||
|
||||
|
||||
while(pos < end) {
|
||||
u32 remaining = end - pos;
|
||||
u32 nb = writeSize - (pos % writeSize);
|
||||
|
||||
|
||||
u32 dataSize = (remaining < nb) ? remaining : nb;
|
||||
|
||||
|
||||
if ((res = type.chip->writeSaveData(type, pos, (void*) ((u8*) data - offset + pos), dataSize))) return res;
|
||||
|
||||
|
||||
pos = ((pos / writeSize) + 1) * writeSize; // truncate
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CardSPIReadSaveData_9bit(CardSPIType type, u32 pos, void* data, u32 size) {
|
||||
int CardSPIReadSaveData_9bit(CardSPIType type, u32 pos, void* data, u32 size) {
|
||||
u8 cmd[4];
|
||||
u32 cmdSize = 2;
|
||||
|
||||
|
||||
u32 end = pos + size;
|
||||
|
||||
|
||||
u32 read = 0;
|
||||
if (pos < 0x100) {
|
||||
u32 len = 0x100 - pos;
|
||||
cmd[0] = SPI_512B_EEPROM_CMD_RDLO;
|
||||
cmd[1] = (u8) pos;
|
||||
|
||||
|
||||
int res = CardSPIWriteRead(type, cmd, cmdSize, data, len, NULL, 0);
|
||||
if (res) return res;
|
||||
|
||||
|
||||
read += len;
|
||||
}
|
||||
|
||||
|
||||
if (end >= 0x100) {
|
||||
u32 len = end - 0x100;
|
||||
|
||||
cmd[0] = SPI_512B_EEPROM_CMD_RDHI;
|
||||
cmd[1] = (u8)(pos + read);
|
||||
|
||||
|
||||
int res = CardSPIWriteRead(type, cmd, cmdSize, (void*)((u8*)data + read), len, NULL, 0);
|
||||
|
||||
if (res) return res;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -335,7 +335,7 @@ int CardSPIReadSaveData_16bit(CardSPIType type, u32 offset, void* data, u32 size
|
||||
|
||||
int CardSPIReadSaveData_24bit(CardSPIType type, u32 offset, void* data, u32 size) {
|
||||
u8 cmd[4] = { SPI_CMD_READ, (u8)(offset >> 16), (u8)(offset >> 8), (u8) offset };
|
||||
|
||||
|
||||
return CardSPIWriteRead(type, cmd, 4, data, size, NULL, 0);
|
||||
}
|
||||
|
||||
@ -343,10 +343,10 @@ int CardSPIReadSaveData(CardSPIType type, u32 offset, void* data, u32 size) {
|
||||
if (type.chip == NO_CHIP) return 1;
|
||||
|
||||
if (size == 0) return 0;
|
||||
|
||||
|
||||
int res = CardSPIWaitWriteEnd(type, 1000);
|
||||
if (res) return res;
|
||||
|
||||
|
||||
size = (size <= CardSPIGetCapacity(type) - offset) ? size : CardSPIGetCapacity(type) - offset;
|
||||
|
||||
return type.chip->readSaveData(type, offset, data, size);
|
||||
@ -358,7 +358,7 @@ int CardSPIEraseSector_emulated(CardSPIType type, u32 offset) {
|
||||
if (!fill_buf) return 1;
|
||||
memset(fill_buf, 0xff, blockSize);
|
||||
offset = (offset / blockSize) * blockSize;
|
||||
|
||||
|
||||
int res = CardSPIWriteSaveData(type, offset, fill_buf, blockSize);
|
||||
free(fill_buf);
|
||||
return res;
|
||||
@ -366,10 +366,10 @@ int CardSPIEraseSector_emulated(CardSPIType type, u32 offset) {
|
||||
|
||||
int CardSPIEraseSector_real(CardSPIType type, u32 offset) {
|
||||
u8 cmd[4] = { type.chip->eraseCommand, (u8)(offset >> 16), (u8)(offset >> 8), (u8) offset };
|
||||
|
||||
|
||||
int res = CardSPIWaitWriteEnd(type, 10000);
|
||||
if (res) return res;
|
||||
|
||||
|
||||
return _SPIWriteTransaction(type, cmd, 4, NULL, 0);
|
||||
}
|
||||
|
||||
@ -399,41 +399,41 @@ int CardSPIErase(CardSPIType type) {
|
||||
*
|
||||
* Copyright (C) Pokedoc (2010)
|
||||
*/
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
|
||||
|
||||
int _SPIIsDataMirrored(CardSPIType type, int size, bool* mirrored) {
|
||||
u32 offset0 = (size-1); // n KB
|
||||
u32 offset1 = (2*size-1); // 2n KB
|
||||
|
||||
|
||||
u8 buf1; // +0k data read -> write
|
||||
u8 buf2; // +n k data read -> read
|
||||
u8 buf3; // +0k ~data write
|
||||
u8 buf4; // +n k data new comp buf2
|
||||
|
||||
|
||||
int res;
|
||||
|
||||
|
||||
if ((res = CardSPIReadSaveData(type, offset0, &buf1, 1))) return res;
|
||||
if ((res = CardSPIReadSaveData(type, offset1, &buf2, 1))) return res;
|
||||
buf3=~buf1;
|
||||
if ((res = CardSPIWriteSaveData(type, offset0, &buf3, 1))) return res;
|
||||
if ((res = CardSPIReadSaveData(type, offset1, &buf4, 1))) return res;
|
||||
if ((res = CardSPIWriteSaveData(type, offset0, &buf1, 1))) return res;
|
||||
|
||||
|
||||
*mirrored = buf2 != buf4;
|
||||
return 0;
|
||||
}
|
||||
@ -442,8 +442,8 @@ int CardSPIGetCardSPIType(CardSPIType* type, bool infrared) {
|
||||
u8 sr = 0;
|
||||
u32 jedec = 0;
|
||||
CardSPIType t = {NO_CHIP, infrared};
|
||||
int res;
|
||||
|
||||
int res;
|
||||
|
||||
if(infrared) {
|
||||
// Infrared carts currently not supported, need additional handling!
|
||||
*type = (CardSPIType) {NO_CHIP, true};
|
||||
@ -452,20 +452,20 @@ int CardSPIGetCardSPIType(CardSPIType* type, bool infrared) {
|
||||
|
||||
res = CardSPIReadJEDECIDAndStatusReg(t, &jedec, &sr);
|
||||
if (res) return res;
|
||||
|
||||
|
||||
if ((sr & 0xfd) == 0x00 && (jedec != 0x00ffffff)) { t.chip = &FLASH_DUMMY; }
|
||||
if ((sr & 0xfd) == 0xF0 && (jedec == 0x00ffffff)) { *type = (CardSPIType) { EEPROM_512B, false }; return 0; }
|
||||
if ((sr & 0xfd) == 0x00 && (jedec == 0x00ffffff)) { t = (CardSPIType) { &EEPROM_DUMMY, false }; }
|
||||
|
||||
|
||||
if(t.chip == NO_CHIP) {
|
||||
*type = (CardSPIType) {NO_CHIP, false};
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if (t.chip == &EEPROM_DUMMY) {
|
||||
bool mirrored = false;
|
||||
size_t i;
|
||||
|
||||
|
||||
for(i = 0; i < sizeof(EEPROMTypes) / sizeof(CardSPITypeData) - 1; i++) {
|
||||
if ((res = _SPIIsDataMirrored(t, CardSPIGetCapacity((CardSPIType) {EEPROMTypes + i, false}), &mirrored))) return res;
|
||||
if (mirrored) {
|
||||
@ -476,15 +476,15 @@ int CardSPIGetCardSPIType(CardSPIType* type, bool infrared) {
|
||||
*type = (CardSPIType) { EEPROMTypes + i, false };
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
for(size_t i = 0; i < sizeof(flashTypes) / sizeof(CardSPITypeData); i++) {
|
||||
if (flashTypes[i].jedecId == jedec) {
|
||||
*type = (CardSPIType) { flashTypes + i, infrared };
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
*type = (CardSPIType) { NO_CHIP, infrared };
|
||||
return 0;
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* This file is based on SPI.h from TWLSaveTool. Its copyright notice is
|
||||
* reproduced below.
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2015-2016 TuxSH
|
||||
*
|
||||
* TWLSaveTool is free software: you can redistribute it and/or modify
|
||||
|
@ -42,9 +42,9 @@ void NTR_CmdReadHeader (u8* buffer)
|
||||
while(REG_NTRCARDROMCNT&NTRCARD_BUSY) ;
|
||||
u32 iCardId=cardReadID(NTRCARD_CLK_SLOW);
|
||||
while(REG_NTRCARDROMCNT&NTRCARD_BUSY) ;
|
||||
|
||||
|
||||
u32 iCheapCard=iCardId&0x80000000;
|
||||
|
||||
|
||||
if(iCheapCard)
|
||||
{
|
||||
//this is magic of wood goblins
|
||||
|
@ -68,18 +68,18 @@ u32 InitCartRead(CartData* cdata) {
|
||||
cdata->cart_type = (cdata->cart_id & 0x10000000) ? CART_CTR : CART_NTR;
|
||||
if (cdata->cart_type & CART_CTR) { // CTR cartridges
|
||||
memset(cdata, 0xFF, 0x4000 + PRIV_HDR_SIZE); // switch the padding to 0xFF
|
||||
|
||||
|
||||
// init, NCCH header
|
||||
static u32 sec_keys[4];
|
||||
u8* ncch_header = cdata->header + 0x1000;
|
||||
CTR_CmdReadHeader(ncch_header);
|
||||
Cart_Secure_Init((u32*) (void*) ncch_header, sec_keys);
|
||||
|
||||
|
||||
// NCSD header and CINFO
|
||||
// Cart_Dummy();
|
||||
// Cart_Dummy();
|
||||
CTR_CmdReadData(0, 0x200, 8, cdata->header);
|
||||
|
||||
|
||||
// safety checks, cart size
|
||||
NcsdHeader* ncsd = (NcsdHeader*) (void*) cdata->header;
|
||||
NcchHeader* ncch = (NcchHeader*) (void*) ncch_header;
|
||||
@ -90,14 +90,14 @@ u32 InitCartRead(CartData* cdata) {
|
||||
if (cdata->cart_size > 0x100000000) return 1; // carts > 4GB don't exist
|
||||
// else if (cdata->cart_size == 0x100000000) cdata->cart_size -= 0x200; // silent 4GB fix
|
||||
if (cdata->data_size > cdata->cart_size) return 1;
|
||||
|
||||
|
||||
// private header
|
||||
u8* priv_header = cdata->header + 0x4000;
|
||||
CTR_CmdReadUniqueID(priv_header);
|
||||
memcpy(priv_header + 0x40, &(cdata->cart_id), 4);
|
||||
memset(priv_header + 0x44, 0x00, 4);
|
||||
memset(priv_header + 0x48, 0xFF, 8);
|
||||
|
||||
|
||||
// save data
|
||||
u32 card2_offset = getle32(cdata->header + 0x200);
|
||||
if ((card2_offset != 0xFFFFFFFF) || (CardSPIGetCardSPIType(&(cdata->save_type), 0) != 0)) {
|
||||
@ -110,13 +110,13 @@ u32 InitCartRead(CartData* cdata) {
|
||||
NTR_CmdReadHeader(cdata->header);
|
||||
if (!(*(cdata->header))) return 1; // error reading the header
|
||||
if (!NTR_Secure_Init(cdata->header, Cart_GetID(), 0)) return 1;
|
||||
|
||||
|
||||
// cartridge size, trimmed size, twl presets
|
||||
if (nds_header->device_capacity >= 15) return 1; // too big, not valid
|
||||
cdata->cart_size = (128 * 1024) << nds_header->device_capacity;
|
||||
cdata->data_size = nds_header->ntr_rom_size;
|
||||
cdata->arm9i_rom_offset = 0;
|
||||
|
||||
|
||||
// TWL header
|
||||
if (nds_header->unit_code != 0x00) { // DSi or NDS+DSi
|
||||
cdata->cart_type |= CART_TWL;
|
||||
@ -129,10 +129,10 @@ u32 InitCartRead(CartData* cdata) {
|
||||
NTR_CmdReadHeader(cdata->twl_header);
|
||||
if (!NTR_Secure_Init(cdata->twl_header, Cart_GetID(), 1)) return 1;
|
||||
}
|
||||
|
||||
|
||||
// last safety check
|
||||
if (cdata->data_size > cdata->cart_size) return 1;
|
||||
|
||||
|
||||
// save data
|
||||
bool infrared = *(nds_header->game_code) == 'I';
|
||||
if (CardSPIGetCardSPIType(&(cdata->save_type), infrared) != 0) {
|
||||
@ -203,7 +203,7 @@ u32 ReadCartSectors(void* buffer, u32 sector, u32 count, CartData* cdata) {
|
||||
}
|
||||
|
||||
u32 ReadCartBytes(void* buffer, u64 offset, u64 count, CartData* cdata) {
|
||||
if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case
|
||||
if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case
|
||||
// simple wrapper function for ReadCartSectors(...)
|
||||
return ReadCartSectors(buffer, offset / 0x200, count / 0x200, cdata);
|
||||
} else { // misaligned data -> -___-
|
||||
|
@ -129,7 +129,7 @@ void NTR_InitKey (u32 aGameCode, u32* pCardHash, int nCardHash, u32* pKeyCode, i
|
||||
const u8* BlowfishNtr = (const u8*)0x01FFE428;
|
||||
memcpy (pCardHash, BlowfishNtr, 0x1048);
|
||||
}
|
||||
|
||||
|
||||
pKeyCode[0] = aGameCode;
|
||||
pKeyCode[1] = aGameCode/2;
|
||||
pKeyCode[2] = aGameCode*2;
|
||||
@ -202,7 +202,7 @@ void NTR_SecureDelay(u16 aTimeout)
|
||||
//TIMER0_CR=TIMER_DIV_256|TIMER_ENABLE;
|
||||
TIMER0_CR=TIMER_DIV_1024|TIMER_ENABLE;
|
||||
while(TIMER0_DATA!=0xFFFF);
|
||||
|
||||
|
||||
// Clear out the timer registers
|
||||
TIMER0_CR=0;
|
||||
TIMER0_DATA=0;
|
||||
|
@ -183,7 +183,7 @@ void CheckBattery(u32* battery, bool* is_charging) {
|
||||
}
|
||||
*battery = battery_l;
|
||||
}
|
||||
|
||||
|
||||
if (is_charging) {
|
||||
static bool is_charging_l = false;
|
||||
static u64 timer_c = (u64) -1;
|
||||
@ -200,19 +200,19 @@ void DrawBatteryBitmap(u16* screen, u32 b_x, u32 b_y, u32 width, u32 height) {
|
||||
const u16 color_inline = COLOR_LIGHTGREY;
|
||||
const u16 color_inside = COLOR_LIGHTERGREY;
|
||||
const u16 color_bg = COLOR_TRANSPARENT;
|
||||
|
||||
|
||||
if ((width < 8) || (height < 6)) return;
|
||||
|
||||
|
||||
u32 battery;
|
||||
bool is_charging;
|
||||
CheckBattery(&battery, &is_charging);
|
||||
|
||||
|
||||
u16 color_battery = (is_charging) ? COLOR_BATTERY_CHARGING :
|
||||
(battery > 70) ? COLOR_BATTERY_FULL : (battery > 30) ? COLOR_BATTERY_MEDIUM : COLOR_BATTERY_LOW;
|
||||
u32 nub_size = (height < 12) ? 1 : 2;
|
||||
u32 width_inside = width - 4 - nub_size;
|
||||
u32 width_battery = (battery >= 100) ? width_inside : ((battery * width_inside) + 50) / 100;
|
||||
|
||||
|
||||
for (u32 y = 0; y < height; y++) {
|
||||
const u32 mirror_y = (y >= (height+1) / 2) ? height - 1 - y : y;
|
||||
for (u32 x = 0; x < width; x++) {
|
||||
@ -233,14 +233,14 @@ void DrawTopBar(const char* curr_path) {
|
||||
const u32 bartxt_x = 2;
|
||||
const u32 len_path = SCREEN_WIDTH_TOP - 120;
|
||||
char tempstr[64];
|
||||
|
||||
|
||||
// top bar - current path
|
||||
DrawRectangle(TOP_SCREEN, 0, 0, SCREEN_WIDTH_TOP, 12, COLOR_TOP_BAR);
|
||||
if (*curr_path) TruncateString(tempstr, curr_path, min(63, len_path / FONT_WIDTH_EXT), 8);
|
||||
else snprintf(tempstr, 16, "[root]");
|
||||
DrawStringF(TOP_SCREEN, bartxt_x, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%s", tempstr);
|
||||
bool show_time = true;
|
||||
|
||||
|
||||
#ifdef SHOW_FREE
|
||||
if (*curr_path) { // free & total storage
|
||||
const u32 bartxt_rx = SCREEN_WIDTH_TOP - (19*FONT_WIDTH_EXT) - bartxt_x;
|
||||
@ -262,14 +262,14 @@ void DrawTopBar(const char* curr_path) {
|
||||
show_time = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (show_time) { // clock & battery
|
||||
const u32 battery_width = 16;
|
||||
const u32 battery_height = 9;
|
||||
const u32 battery_x = SCREEN_WIDTH_TOP - battery_width - bartxt_x;
|
||||
const u32 battery_y = (12 - battery_height) / 2;
|
||||
const u32 clock_x = battery_x - (15*FONT_WIDTH_EXT);
|
||||
|
||||
|
||||
char timestr[32];
|
||||
GetTimeString(timestr, false, false);
|
||||
DrawStringF(TOP_SCREEN, clock_x, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%14.14s", timestr);
|
||||
@ -284,7 +284,7 @@ void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, u32 curr_pan
|
||||
const u32 len_info = (SCREEN_WIDTH_MAIN - ((SCREEN_WIDTH_MAIN >= 400) ? 80 : 20)) / 2;
|
||||
const u32 str_len_info = min(63, len_info / FONT_WIDTH_EXT);
|
||||
char tempstr[64];
|
||||
|
||||
|
||||
static u32 state_prev = 0xFFFFFFFF;
|
||||
u32 state_curr =
|
||||
((*curr_path) ? (1<<0) : 0) |
|
||||
@ -293,12 +293,12 @@ void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, u32 curr_pan
|
||||
((GetMountState()) ? (1<<3) : 0) |
|
||||
((GetWritePermissions() > PERM_BASE) ? (1<<4) : 0) |
|
||||
(curr_pane<<5);
|
||||
|
||||
|
||||
if (state_prev != state_curr) {
|
||||
ClearScreenF(true, false, COLOR_STD_BG);
|
||||
state_prev = state_curr;
|
||||
}
|
||||
|
||||
|
||||
// left top - current file info
|
||||
if (curr_pane) snprintf(tempstr, 63, "PANE #%lu", curr_pane);
|
||||
else snprintf(tempstr, 63, "CURRENT");
|
||||
@ -315,7 +315,7 @@ void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, u32 curr_pan
|
||||
} else if (curr_entry->type == T_ROOT) {
|
||||
int drvtype = DriveType(curr_entry->path);
|
||||
char drvstr[32];
|
||||
snprintf(drvstr, 31, "(%s%s)",
|
||||
snprintf(drvstr, 31, "(%s%s)",
|
||||
((drvtype & DRV_SDCARD) ? "SD" : (drvtype & DRV_RAMDRIVE) ? "RAMdrive" : (drvtype & DRV_GAME) ? "Game" :
|
||||
(drvtype & DRV_SYSNAND) ? "SysNAND" : (drvtype & DRV_EMUNAND) ? "EmuNAND" : (drvtype & DRV_IMAGE) ? "Image" :
|
||||
(drvtype & DRV_XORPAD) ? "XORpad" : (drvtype & DRV_MEMORY) ? "Memory" : (drvtype & DRV_ALIAS) ? "Alias" :
|
||||
@ -341,7 +341,7 @@ void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, u32 curr_pan
|
||||
ResizeString(tempstr, "", str_len_info, 8, false);
|
||||
DrawStringF(MAIN_SCREEN, 4, info_start + 12 + 10 + 10, color_current, COLOR_STD_BG, "%s", tempstr);
|
||||
}
|
||||
|
||||
|
||||
// right top - clipboard
|
||||
DrawStringF(MAIN_SCREEN, SCREEN_WIDTH_MAIN - len_info, info_start, COLOR_STD_FONT, COLOR_STD_BG, "%*s",
|
||||
len_info / FONT_WIDTH_EXT, (clipboard->n_entries) ? "[CLIPBOARD]" : "");
|
||||
@ -354,7 +354,7 @@ void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, u32 curr_pan
|
||||
if (clipboard->n_entries > n_cb_show) snprintf(tempstr, 60, "+ %lu more", clipboard->n_entries - n_cb_show);
|
||||
DrawStringF(MAIN_SCREEN, SCREEN_WIDTH_MAIN - len_info - 4, info_start + 12 + (n_cb_show*10), COLOR_DARKGREY, COLOR_STD_BG,
|
||||
"%*s", len_info / FONT_WIDTH_EXT, tempstr);
|
||||
|
||||
|
||||
// bottom: instruction block
|
||||
char instr[512];
|
||||
snprintf(instr, 512, "%s\n%s%s%s%s%s%s%s%s",
|
||||
@ -364,7 +364,7 @@ void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, u32 curr_pan
|
||||
((GetWritePermissions() > PERM_BASE) ? "R+Y - Relock write permissions\n" : ""),
|
||||
(*curr_path) ? "" : (GetMountState()) ? "R+X - Unmount image\n" : "",
|
||||
(*curr_path) ? "" : (CheckSDMountState()) ? "R+B - Unmount SD card\n" : "R+B - Remount SD card\n",
|
||||
(*curr_path) ? "R+A - Directory options\n" : "R+A - Drive options\n",
|
||||
(*curr_path) ? "R+A - Directory options\n" : "R+A - Drive options\n",
|
||||
"R+L - Make a Screenshot\n",
|
||||
"R+\x1B\x1A - Switch to prev/next pane\n",
|
||||
(clipboard->n_entries) ? "SELECT - Clear Clipboard\n" : "SELECT - Restore Clipboard\n", // only if clipboard is full
|
||||
@ -379,12 +379,12 @@ void DrawDirContents(DirStruct* contents, u32 cursor, u32* scroll) {
|
||||
const u32 pos_x = 0;
|
||||
const u32 lines = (SCREEN_HEIGHT-(start_y+2)+(stp_y-1)) / stp_y;
|
||||
u32 pos_y = start_y + 2;
|
||||
|
||||
|
||||
if (*scroll > cursor) *scroll = cursor;
|
||||
else if (*scroll + lines <= cursor) *scroll = cursor - lines + 1;
|
||||
if (*scroll + lines > contents->n_entries)
|
||||
*scroll = (contents->n_entries > lines) ? contents->n_entries - lines : 0;
|
||||
|
||||
|
||||
for (u32 i = 0; pos_y < SCREEN_HEIGHT; i++) {
|
||||
char tempstr[str_width + 1];
|
||||
u32 offset_i = *scroll + i;
|
||||
@ -402,7 +402,7 @@ void DrawDirContents(DirStruct* contents, u32 cursor, u32* scroll) {
|
||||
DrawStringF(ALT_SCREEN, pos_x, pos_y, color_font, COLOR_STD_BG, "%s", tempstr);
|
||||
pos_y += stp_y;
|
||||
}
|
||||
|
||||
|
||||
const u32 flist_height = (SCREEN_HEIGHT - start_y);
|
||||
const u32 bar_width = 2;
|
||||
if (contents->n_entries > lines) { // draw position bar at the right
|
||||
@ -410,7 +410,7 @@ void DrawDirContents(DirStruct* contents, u32 cursor, u32* scroll) {
|
||||
u32 bar_height = (lines * flist_height) / contents->n_entries;
|
||||
if (bar_height < bar_height_min) bar_height = bar_height_min;
|
||||
const u32 bar_pos = ((u64) *scroll * (flist_height - bar_height)) / (contents->n_entries - lines) + start_y;
|
||||
|
||||
|
||||
DrawRectangle(ALT_SCREEN, SCREEN_WIDTH_ALT - bar_width, start_y, bar_width, (bar_pos - start_y), COLOR_STD_BG);
|
||||
DrawRectangle(ALT_SCREEN, SCREEN_WIDTH_ALT - bar_width, bar_pos + bar_height, bar_width, SCREEN_HEIGHT - (bar_pos + bar_height), COLOR_STD_BG);
|
||||
DrawRectangle(ALT_SCREEN, SCREEN_WIDTH_ALT - bar_width, bar_pos, bar_width, bar_height, COLOR_SIDE_BAR);
|
||||
@ -431,14 +431,14 @@ u32 SdFormatMenu(const char* slabel) {
|
||||
u64 sdcard_size_mb = 0;
|
||||
u64 emunand_size_mb = (u64) -1;
|
||||
u32 user_select;
|
||||
|
||||
|
||||
// check actual SD card size
|
||||
sdcard_size_mb = GetSDCardSize() / 0x100000;
|
||||
if (!sdcard_size_mb) {
|
||||
ShowPrompt(false, "Error: SD card not detected.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
user_select = ShowSelectPrompt(7, option_emunand_size, "Format SD card (%lluMB)?\nChoose EmuNAND size:", sdcard_size_mb);
|
||||
if (user_select && (user_select < 4)) {
|
||||
emunand_size_mb = (user_select == 2) ? sysnand_min_size_mb : (user_select == 3) ? sysnand_size_mb : 0;
|
||||
@ -450,20 +450,20 @@ u32 SdFormatMenu(const char* slabel) {
|
||||
if (emunand_size_mb == (u64) -1) break;
|
||||
} while (emunand_size_mb > sdcard_size_mb);
|
||||
if (emunand_size_mb == (u64) -1) return 1;
|
||||
|
||||
|
||||
user_select = ShowSelectPrompt(4, option_cluster_size, "Format SD card (%lluMB)?\nChoose cluster size:", sdcard_size_mb);
|
||||
if (!user_select) return 1;
|
||||
else cluster_size = cluster_size_table[user_select];
|
||||
|
||||
|
||||
snprintf(label, DRV_LABEL_LEN + 4, "0:%s", (slabel && *slabel) ? slabel : "GM9SD");
|
||||
if (!ShowKeyboardOrPrompt(label + 2, 11 + 1, "Format SD card (%lluMB)?\nEnter label:", sdcard_size_mb))
|
||||
return 1;
|
||||
|
||||
|
||||
if (!FormatSDCard(emunand_size_mb, cluster_size, label)) {
|
||||
ShowPrompt(false, "Format SD: failed!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
if (emunand_size_mb >= sysnand_min_size_mb) {
|
||||
u32 emunand_offset = 1;
|
||||
u32 n_emunands = 1;
|
||||
@ -480,13 +480,13 @@ u32 SdFormatMenu(const char* slabel) {
|
||||
emunand_offset = (user_select == 2) ? 0 : 1; // 0 -> GW EmuNAND
|
||||
} else user_select = ShowPrompt(true, "Clone SysNAND to RedNAND?") ? 1 : 0;
|
||||
if (!user_select) return 0;
|
||||
|
||||
|
||||
u8 ncsd[0x200];
|
||||
u32 flags = OVERRIDE_PERM;
|
||||
InitSDCardFS(); // this has to be initialized for EmuNAND to work
|
||||
for (u32 i = 0; i < n_emunands; i++) {
|
||||
if ((i * sysnand_multi_size_mb) + sysnand_min_size_mb > emunand_size_mb) break;
|
||||
SetEmuNandBase((i * sysnand_multi_size_mb * 0x100000 / 0x200) + emunand_offset);
|
||||
SetEmuNandBase((i * sysnand_multi_size_mb * 0x100000 / 0x200) + emunand_offset);
|
||||
if ((ReadNandSectors(ncsd, 0, 1, 0xFF, NAND_SYSNAND) != 0) ||
|
||||
(WriteNandSectors(ncsd, 0, 1, 0xFF, NAND_EMUNAND) != 0) ||
|
||||
(!PathCopy("E:", "S:/nand_minsize.bin", &flags))) {
|
||||
@ -496,7 +496,7 @@ u32 SdFormatMenu(const char* slabel) {
|
||||
}
|
||||
DeinitSDCardFS();
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -539,51 +539,51 @@ u32 FileHexViewer(const char* path) {
|
||||
u8* data = NULL;
|
||||
u8* bottom_cpy = (u8*) malloc(SCREEN_SIZE_BOT); // a copy of the bottom screen framebuffer
|
||||
u32 fsize = FileGetSize(path);
|
||||
|
||||
|
||||
bool dual_screen = 0;
|
||||
int x_off = 0, x_hex = 0, x_ascii = 0;
|
||||
u32 vpad = 0, hlpad = 0, hrpad = 0;
|
||||
u32 rows = 0, cols = 0;
|
||||
u32 total_shown = 0;
|
||||
u32 total_data = 0;
|
||||
|
||||
|
||||
u32 last_mode = 0xFF;
|
||||
u32 last_offset = (u32) -1;
|
||||
u32 offset = 0;
|
||||
|
||||
|
||||
u8 found_data[64 + 1] = { 0 };
|
||||
u32 found_offset = (u32) -1;
|
||||
u32 found_size = 0;
|
||||
|
||||
|
||||
static const u32 edit_bsize = 0x4000; // should be multiple of 0x200 * 2
|
||||
bool edit_mode = false;
|
||||
u8* buffer = (u8*) malloc(edit_bsize);
|
||||
u8* buffer_cpy = (u8*) malloc(edit_bsize);
|
||||
u32 edit_start = 0;
|
||||
int cursor = 0;
|
||||
|
||||
|
||||
if (!bottom_cpy || !buffer || !buffer_cpy) {
|
||||
if (bottom_cpy) free(bottom_cpy);
|
||||
if (buffer) free(buffer);
|
||||
if (buffer_cpy) free(buffer_cpy);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static bool show_instr = true;
|
||||
static const char* instr = "Hexeditor Controls:\n \n\x18\x19\x1A\x1B(+R) - Scroll\nR+Y - Switch view\nX - Search / goto...\nA - Enter edit mode\nA+\x18\x19\x1A\x1B - Edit value\nB - Exit\n";
|
||||
if (show_instr) { // show one time instructions
|
||||
ShowPrompt(false, instr);
|
||||
show_instr = false;
|
||||
}
|
||||
|
||||
|
||||
if (MAIN_SCREEN != TOP_SCREEN) ShowString(instr);
|
||||
memcpy(bottom_cpy, BOT_SCREEN, SCREEN_SIZE_BOT);
|
||||
|
||||
|
||||
data = buffer;
|
||||
while (true) {
|
||||
if (mode != last_mode) {
|
||||
if (FONT_WIDTH_EXT <= 5) {
|
||||
mode = 0;
|
||||
mode = 0;
|
||||
vpad = 1;
|
||||
hlpad = hrpad = 2;
|
||||
cols = 16;
|
||||
@ -602,7 +602,7 @@ u32 FileHexViewer(const char* path) {
|
||||
x_hex = x_off + (8*FONT_WIDTH_EXT) + 16;
|
||||
dual_screen = false;
|
||||
} else {
|
||||
mode = 0;
|
||||
mode = 0;
|
||||
vpad = 0;
|
||||
hlpad = hrpad = 3;
|
||||
cols = 8;
|
||||
@ -641,7 +641,7 @@ u32 FileHexViewer(const char* path) {
|
||||
dual_screen = false;
|
||||
break;
|
||||
default:
|
||||
mode = 0;
|
||||
mode = 0;
|
||||
vpad = hlpad = hrpad = 2;
|
||||
cols = 8;
|
||||
x_off = (SCREEN_WIDTH_TOP - SCREEN_WIDTH_BOT) / 2;
|
||||
@ -673,7 +673,7 @@ u32 FileHexViewer(const char* path) {
|
||||
}
|
||||
last_offset = offset;
|
||||
}
|
||||
|
||||
|
||||
// display data on screen
|
||||
for (u32 row = 0; row < rows; row++) {
|
||||
char ascii[16 + 1] = { 0 };
|
||||
@ -682,29 +682,29 @@ u32 FileHexViewer(const char* path) {
|
||||
u32 cutoff = (curr_pos >= total_data) ? 0 : (total_data >= curr_pos + cols) ? cols : total_data - curr_pos;
|
||||
u16* screen = TOP_SCREEN;
|
||||
u32 x0 = 0;
|
||||
|
||||
|
||||
// marked offsets handling
|
||||
s32 marked0 = 0, marked1 = 0;
|
||||
if ((found_size > 0) &&
|
||||
(found_offset + found_size > offset + curr_pos) &&
|
||||
(found_offset + found_size > offset + curr_pos) &&
|
||||
(found_offset < offset + curr_pos + cols)) {
|
||||
marked0 = (s32) found_offset - (offset + curr_pos);
|
||||
marked1 = marked0 + found_size;
|
||||
if (marked0 < 0) marked0 = 0;
|
||||
if (marked1 > cols) marked1 = cols;
|
||||
}
|
||||
|
||||
|
||||
// switch to bottom screen
|
||||
if (y >= SCREEN_HEIGHT) {
|
||||
y -= SCREEN_HEIGHT;
|
||||
screen = BOT_SCREEN;
|
||||
x0 = 40;
|
||||
}
|
||||
|
||||
|
||||
memcpy(ascii, data + curr_pos, cols);
|
||||
for (u32 col = 0; col < cols; col++)
|
||||
if ((col >= cutoff) || (ascii[col] == 0x00)) ascii[col] = ' ';
|
||||
|
||||
|
||||
// draw offset / ASCII representation
|
||||
if (x_off >= 0) DrawStringF(screen, x_off - x0, y, cutoff ? COLOR_HVOFFS : COLOR_HVOFFSI,
|
||||
COLOR_STD_BG, "%08X", (unsigned int) offset + curr_pos);
|
||||
@ -715,7 +715,7 @@ u32 FileHexViewer(const char* path) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
// draw HEX values
|
||||
for (u32 col = 0; (col < cols) && (x_hex >= 0); col++) {
|
||||
u32 x = (x_hex + hlpad) + (((2*FONT_WIDTH_EXT) + hrpad + hlpad) * col) - x0;
|
||||
@ -726,7 +726,7 @@ u32 FileHexViewer(const char* path) {
|
||||
else DrawStringF(screen, x, y, hex_color, COLOR_STD_BG, " ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// handle user input
|
||||
u32 pad_state = InputWait(0);
|
||||
if (!edit_mode) { // standard viewer mode
|
||||
@ -750,7 +750,7 @@ u32 FileHexViewer(const char* path) {
|
||||
else memcpy(BOT_SCREEN, bottom_cpy, SCREEN_SIZE_BOT);
|
||||
} else if (pad_state & BUTTON_X) {
|
||||
static const char* optionstr[3] = { "Go to offset", "Search for string", "Search for data" };
|
||||
u32 user_select = ShowSelectPrompt(3, optionstr, "Current offset: %08X\nSelect action:",
|
||||
u32 user_select = ShowSelectPrompt(3, optionstr, "Current offset: %08X\nSelect action:",
|
||||
(unsigned int) offset);
|
||||
if (user_select == 1) { // -> goto offset
|
||||
u64 new_offset = ShowHexPrompt(offset, 8, "Current offset: %08X\nEnter new offset below.",
|
||||
@ -785,7 +785,7 @@ u32 FileHexViewer(const char* path) {
|
||||
found_size = 0;
|
||||
found_offset = (u32) -1;
|
||||
cursor = 0;
|
||||
edit_start = ((offset - (offset % 0x200) <= (edit_bsize / 2)) || (fsize < edit_bsize)) ? 0 :
|
||||
edit_start = ((offset - (offset % 0x200) <= (edit_bsize / 2)) || (fsize < edit_bsize)) ? 0 :
|
||||
offset - (offset % 0x200) - (edit_bsize / 2);
|
||||
FileGetData(path, buffer, edit_bsize, edit_start);
|
||||
memcpy(buffer_cpy, buffer, edit_bsize);
|
||||
@ -831,11 +831,11 @@ u32 FileHexViewer(const char* path) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ClearScreen(TOP_SCREEN, COLOR_STD_BG);
|
||||
if (MAIN_SCREEN == TOP_SCREEN) memcpy(BOT_SCREEN, bottom_cpy, SCREEN_SIZE_BOT);
|
||||
else ClearScreen(BOT_SCREEN, COLOR_STD_BG);
|
||||
|
||||
|
||||
free(bottom_cpy);
|
||||
free(buffer);
|
||||
free(buffer_cpy);
|
||||
@ -855,7 +855,7 @@ u32 Sha256Calculator(const char* path) {
|
||||
static u8 sha256_prev[32] = { 0 };
|
||||
char sha_path[256];
|
||||
u8 sha256_file[32];
|
||||
|
||||
|
||||
snprintf(sha_path, 256, "%s.sha", path);
|
||||
bool have_sha = (FileGetData(sha_path, sha256_file, 32, 0) == 32);
|
||||
bool match_sha = have_sha && (memcmp(sha256, sha256_file, 32) == 0);
|
||||
@ -870,11 +870,11 @@ u32 Sha256Calculator(const char* path) {
|
||||
(write_sha) ? "\n \nWrite .SHA file?" : "") && write_sha) {
|
||||
FileSetData(sha_path, sha256, 32, 0, true);
|
||||
}
|
||||
|
||||
|
||||
strncpy(pathstr_prev, pathstr, 32 + 1);
|
||||
memcpy(sha256_prev, sha256, 32);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -906,8 +906,8 @@ u32 CmacCalculator(const char* path) {
|
||||
ShowPrompt(false, "Fixing CMAC: failed!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -915,16 +915,16 @@ u32 StandardCopy(u32* cursor, u32* scroll) {
|
||||
DirEntry* curr_entry = &(current_dir->entry[*cursor]);
|
||||
u32 n_marked = 0;
|
||||
if (curr_entry->marked) {
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++)
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++)
|
||||
if (current_dir->entry[i].marked) n_marked++;
|
||||
}
|
||||
|
||||
|
||||
u32 flags = BUILD_PATH;
|
||||
if ((n_marked > 1) && ShowPrompt(true, "Copy all %lu selected items?", n_marked)) {
|
||||
u32 n_success = 0;
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
||||
const char* path = current_dir->entry[i].path;
|
||||
if (!current_dir->entry[i].marked)
|
||||
if (!current_dir->entry[i].marked)
|
||||
continue;
|
||||
flags |= ASK_ALL;
|
||||
DrawDirContents(current_dir, (*cursor = i), scroll);
|
||||
@ -945,7 +945,7 @@ u32 StandardCopy(u32* cursor, u32* scroll) {
|
||||
ShowPrompt(false, "%s\nFailed copying item", pathstr);
|
||||
else ShowPrompt(false, "%s\nCopied to %s", pathstr, OUTPUT_PATH);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -960,7 +960,7 @@ u32 DirFileAttrMenu(const char* path, const char *name) {
|
||||
TruncateString(namestr, name, 31, 8);
|
||||
|
||||
// preparations: create file info, date string
|
||||
if (!drv) {
|
||||
if (!drv) {
|
||||
if (fvx_stat(path, &fno) != FR_OK) return 1;
|
||||
vrt = (fno.fattrib & AM_VRT);
|
||||
new_attrib = fno.fattrib;
|
||||
@ -1071,18 +1071,18 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
const char* file_path = (&(current_dir->entry[*cursor]))->path;
|
||||
const char* file_name = (&(current_dir->entry[*cursor]))->name;
|
||||
const char* optionstr[16];
|
||||
|
||||
|
||||
// check for file lock
|
||||
if (!FileUnlock(file_path)) return 1;
|
||||
|
||||
|
||||
u64 filetype = IdentifyFileType(file_path);
|
||||
u32 drvtype = DriveType(file_path);
|
||||
|
||||
|
||||
bool in_output_path = (strncasecmp(current_path, OUTPUT_PATH, 256) == 0);
|
||||
|
||||
|
||||
// don't handle TMDs inside the game drive, won't work properly anyways
|
||||
if ((filetype & GAME_TMD) && (drvtype & DRV_GAME)) filetype &= ~GAME_TMD;
|
||||
|
||||
|
||||
// special stuff, only available for known filetypes (see int special below)
|
||||
bool mountable = (FTYPE_MOUNTABLE(filetype) && !(drvtype & DRV_IMAGE) &&
|
||||
!((drvtype & (DRV_SYSNAND|DRV_EMUNAND)) && (drvtype & DRV_VIRTUAL) && (filetype & IMG_FAT)));
|
||||
@ -1100,7 +1100,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
bool key_buildable = (FTYPE_KEYBUILD(filetype)) && !in_output_path &&
|
||||
!((drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND));
|
||||
bool titleinfo = (FTYPE_TITLEINFO(filetype));
|
||||
bool ciacheckable = (FTYPE_CIACHECK(filetype));
|
||||
bool ciacheckable = (FTYPE_CIACHECK(filetype));
|
||||
bool renamable = (FTYPE_RENAMABLE(filetype)) && !(drvtype & DRV_VIRTUAL) && !(drvtype & DRV_ALIAS) &&
|
||||
!(drvtype & DRV_CTRNAND) && !(drvtype & DRV_TWLNAND) && !(drvtype & DRV_IMAGE);
|
||||
bool trimable = (FTYPE_TRIMABLE(filetype)) && !(drvtype & DRV_VIRTUAL) && !(drvtype & DRV_ALIAS) &&
|
||||
@ -1122,7 +1122,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
bool installable = (FTYPE_INSTALLABLE(filetype));
|
||||
bool agbexportable = (FTYPE_AGBSAVE(filetype) && (drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND));
|
||||
bool agbimportable = (FTYPE_AGBSAVE(filetype) && (drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND));
|
||||
|
||||
|
||||
char cxi_path[256] = { 0 }; // special options for TMD
|
||||
if ((filetype & GAME_TMD) && !(filetype & FLAG_NUSCDN) &&
|
||||
(GetTmdContentPath(cxi_path, file_path) == 0) &&
|
||||
@ -1131,18 +1131,18 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
mountable = (FTYPE_MOUNTABLE(filetype_cxi) && !(drvtype & DRV_IMAGE));
|
||||
extrcodeable = (FTYPE_HASCODE(filetype_cxi));
|
||||
}
|
||||
|
||||
|
||||
bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || trimable || transferable || hsinjectable || restorable || xorpadable || ebackupable || ncsdfixable || extrcodeable || keyinitable || keyinstallable || bootable || scriptable || fontable || viewable || installable || agbexportable || agbimportable;
|
||||
|
||||
|
||||
char pathstr[32+1];
|
||||
TruncateString(pathstr, file_path, 32, 8);
|
||||
|
||||
|
||||
u32 n_marked = 0;
|
||||
if ((&(current_dir->entry[*cursor]))->marked) {
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++)
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++)
|
||||
if (current_dir->entry[i].marked) n_marked++;
|
||||
}
|
||||
|
||||
|
||||
// main menu processing
|
||||
int n_opt = 0;
|
||||
int special = (special_opt) ? ++n_opt : -1;
|
||||
@ -1199,7 +1199,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
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);
|
||||
if (user_select == hexviewer) { // -> show in hex viewer
|
||||
@ -1307,7 +1307,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
else if (user_select != special) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// stuff for special menu starts here
|
||||
n_opt = 0;
|
||||
int show_info = (titleinfo) ? ++n_opt : -1;
|
||||
@ -1378,7 +1378,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
if (agbexport > 0) optionstr[agbexport-1] = "Dump GBA VC save";
|
||||
if (agbimport > 0) optionstr[agbimport-1] = "Inject GBA VC save";
|
||||
if (setup > 0) optionstr[setup-1] = "Set as default";
|
||||
|
||||
|
||||
// auto select when there is only one option
|
||||
user_select = (n_opt <= 1) ? n_opt : (int) ShowSelectPrompt(n_opt, optionstr, (n_marked > 1) ?
|
||||
"%s\n%(%lu files selected)" : "%s", pathstr, n_marked);
|
||||
@ -1387,13 +1387,13 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
if (clipboard->n_entries && (DriveType(clipboard->entry[0].path) & DRV_IMAGE))
|
||||
clipboard->n_entries = 0; // remove last mounted image clipboard entries
|
||||
InitImgFS((filetype & GAME_TMD) ? cxi_path : file_path);
|
||||
|
||||
|
||||
const char* drv_path = NULL; // find path of mounted drive
|
||||
for (u32 i = 0; i < (sizeof(mnt_drv_paths) / sizeof(const char*)); i++) {
|
||||
if (DriveType((drv_path = mnt_drv_paths[i]))) break;
|
||||
drv_path = NULL;
|
||||
}
|
||||
|
||||
|
||||
if (!drv_path) {
|
||||
ShowPrompt(false, "Mounting image: failed");
|
||||
InitImgFS(NULL);
|
||||
@ -1405,7 +1405,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
(*pane)->scroll = *scroll;
|
||||
if (++*pane >= panedata + N_PANES) *pane -= N_PANES;
|
||||
}
|
||||
|
||||
|
||||
strncpy(current_path, drv_path, 256);
|
||||
GetDirContents(current_dir, current_path);
|
||||
*cursor = 1;
|
||||
@ -1430,7 +1430,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
ShowString("Trying to decrypt %lu files...", n_marked);
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
||||
const char* path = current_dir->entry[i].path;
|
||||
if (!current_dir->entry[i].marked)
|
||||
if (!current_dir->entry[i].marked)
|
||||
continue;
|
||||
if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) {
|
||||
n_other++;
|
||||
@ -1483,7 +1483,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
ShowString("Trying to encrypt %lu files...", n_marked);
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
||||
const char* path = current_dir->entry[i].path;
|
||||
if (!current_dir->entry[i].marked)
|
||||
if (!current_dir->entry[i].marked)
|
||||
continue;
|
||||
if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) {
|
||||
n_other++;
|
||||
@ -1518,10 +1518,10 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
bool force_legit = (user_select == cia_build_legit);
|
||||
if ((n_marked > 1) && ShowPrompt(true, "Try to process all %lu selected files?", n_marked)) {
|
||||
u32 n_success = 0;
|
||||
u32 n_other = 0;
|
||||
u32 n_other = 0;
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
||||
const char* path = current_dir->entry[i].path;
|
||||
if (!current_dir->entry[i].marked)
|
||||
if (!current_dir->entry[i].marked)
|
||||
continue;
|
||||
if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) {
|
||||
n_other++;
|
||||
@ -1579,7 +1579,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
ShowString("Trying to install %lu files...", n_marked);
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
||||
const char* path = current_dir->entry[i].path;
|
||||
if (!current_dir->entry[i].marked)
|
||||
if (!current_dir->entry[i].marked)
|
||||
continue;
|
||||
if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) {
|
||||
n_other++;
|
||||
@ -1617,7 +1617,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
optionstr[0] = "Keep ticket & savegame";
|
||||
optionstr[1] = "Uninstall everything";
|
||||
optionstr[2] = "Abort uninstall";
|
||||
user_select = (int) (n_marked > 1) ?
|
||||
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);
|
||||
@ -1655,7 +1655,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
u32 n_processed = 0;
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
||||
const char* path = current_dir->entry[i].path;
|
||||
if (!current_dir->entry[i].marked)
|
||||
if (!current_dir->entry[i].marked)
|
||||
continue;
|
||||
if (!(filetype & (GAME_CIA|GAME_TMD|GAME_NCSD|GAME_NCCH)) &&
|
||||
!ShowProgress(n_processed++, n_marked, path)) break;
|
||||
@ -1679,7 +1679,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
}
|
||||
if (n_other) ShowPrompt(false, "%lu/%lu files verified ok\n%lu/%lu not of same type",
|
||||
n_success, n_marked, n_other, n_marked);
|
||||
else ShowPrompt(false, "%lu/%lu files verified ok", n_success, n_marked);
|
||||
else ShowPrompt(false, "%lu/%lu files verified ok", n_success, n_marked);
|
||||
} else {
|
||||
ShowString("%s\nVerifying file, please wait...", pathstr);
|
||||
if (filetype & IMG_NAND) {
|
||||
@ -1712,9 +1712,9 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
if (BuildTitleKeyInfo(NULL, dec, true) == 0) {
|
||||
if (n_other) ShowPrompt(false, "%s\n%lu/%lu files processed\n%lu/%lu files ignored",
|
||||
path_out, n_success, n_marked, n_other, n_marked);
|
||||
else ShowPrompt(false, "%s\n%lu/%lu files processed", path_out, n_success, n_marked);
|
||||
else ShowPrompt(false, "%s\n%lu/%lu files processed", path_out, n_success, n_marked);
|
||||
} else ShowPrompt(false, "%s\nBuild database failed.", path_out);
|
||||
} else ShowPrompt(false, "%s\nBuild database %s.", path_out,
|
||||
} else ShowPrompt(false, "%s\nBuild database %s.", path_out,
|
||||
(BuildTitleKeyInfo(file_path, dec, true) == 0) ? "success" : "failed");
|
||||
return 0;
|
||||
}
|
||||
@ -1739,9 +1739,9 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
if (BuildKeyDb(NULL, true) == 0) {
|
||||
if (n_other) ShowPrompt(false, "%s\n%lu/%lu files processed\n%lu/%lu files ignored",
|
||||
path_out, n_success, n_marked, n_other, n_marked);
|
||||
else ShowPrompt(false, "%s\n%lu/%lu files processed", path_out, n_success, n_marked);
|
||||
else ShowPrompt(false, "%s\n%lu/%lu files processed", path_out, n_success, n_marked);
|
||||
} else ShowPrompt(false, "%s\nBuild database failed.", path_out);
|
||||
} else ShowPrompt(false, "%s\nBuild database %s.", path_out,
|
||||
} else ShowPrompt(false, "%s\nBuild database %s.", path_out,
|
||||
(BuildKeyDb(file_path, true) == 0) ? "success" : "failed");
|
||||
return 0;
|
||||
}
|
||||
@ -1755,7 +1755,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
||||
const char* path = current_dir->entry[i].path;
|
||||
u64 prevsize = 0;
|
||||
if (!current_dir->entry[i].marked)
|
||||
if (!current_dir->entry[i].marked)
|
||||
continue;
|
||||
if (!ShowProgress(n_processed++, n_marked, path)) break;
|
||||
if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) {
|
||||
@ -1858,7 +1858,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
u32 n_processed = 0;
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++) {
|
||||
const char* path = current_dir->entry[i].path;
|
||||
if (!current_dir->entry[i].marked)
|
||||
if (!current_dir->entry[i].marked)
|
||||
continue;
|
||||
if (!ShowProgress(n_processed++, n_marked, path)) break;
|
||||
if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) {
|
||||
@ -1868,7 +1868,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
DrawDirContents(current_dir, (*cursor = i), scroll);
|
||||
if (filetype & GAME_TMD) {
|
||||
char cxi_pathl[256] = { 0 };
|
||||
if ((GetTmdContentPath(cxi_pathl, path) == 0) && PathExist(cxi_pathl) &&
|
||||
if ((GetTmdContentPath(cxi_pathl, path) == 0) && PathExist(cxi_pathl) &&
|
||||
(ExtractCodeFromCxiFile(cxi_pathl, NULL, NULL) == 0)) {
|
||||
n_success++;
|
||||
} else continue;
|
||||
@ -1880,7 +1880,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
}
|
||||
if (n_other) ShowPrompt(false, "%lu/%lu files extracted ok\n%lu/%lu not of same type",
|
||||
n_success, n_marked, n_other, n_marked);
|
||||
else ShowPrompt(false, "%lu/%lu files extracted ok", n_success, n_marked);
|
||||
else ShowPrompt(false, "%lu/%lu files extracted ok", n_success, n_marked);
|
||||
} else {
|
||||
char extstr[8] = { 0 };
|
||||
ShowString("%s\nExtracting .code, please wait...", pathstr);
|
||||
@ -1926,7 +1926,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
bool inplace = (user_select == xorpad_inplace);
|
||||
bool success = (BuildNcchInfoXorpads((inplace) ? current_path : OUTPUT_PATH, file_path) == 0);
|
||||
ShowPrompt(false, "%s\nNCCHinfo padgen %s%s", pathstr,
|
||||
(success) ? "success" : "failed",
|
||||
(success) ? "success" : "failed",
|
||||
(!success || inplace) ? "" : "\nOutput dir: " OUTPUT_PATH);
|
||||
GetDirContents(current_dir, current_path);
|
||||
for (; *cursor < current_dir->n_entries; (*cursor)++) {
|
||||
@ -2019,14 +2019,14 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
return FileHandlerMenu(current_path, cursor, scroll, pane);
|
||||
}
|
||||
|
||||
u32 HomeMoreMenu(char* current_path) {
|
||||
NandPartitionInfo np_info;
|
||||
if (GetNandPartitionInfo(&np_info, NP_TYPE_BONUS, NP_SUBTYPE_CTR, 0, NAND_SYSNAND) != 0) np_info.count = 0;
|
||||
|
||||
|
||||
const char* optionstr[8];
|
||||
const char* promptstr = "HOME more... menu.\nSelect action:";
|
||||
u32 n_opt = 0;
|
||||
@ -2040,7 +2040,7 @@ u32 HomeMoreMenu(char* current_path) {
|
||||
int calib = ++n_opt;
|
||||
int sysinfo = ++n_opt;
|
||||
int readme = (FindVTarFileInfo(VRAM0_README_MD, NULL)) ? (int) ++n_opt : -1;
|
||||
|
||||
|
||||
if (sdformat > 0) optionstr[sdformat - 1] = "SD format menu";
|
||||
if (bonus > 0) optionstr[bonus - 1] = "Bonus drive setup";
|
||||
if (multi > 0) optionstr[multi - 1] = "Switch EmuNAND";
|
||||
@ -2051,7 +2051,7 @@ u32 HomeMoreMenu(char* current_path) {
|
||||
if (calib > 0) optionstr[calib - 1] = "Calibrate touchscreen";
|
||||
if (sysinfo > 0) optionstr[sysinfo - 1] = "System info";
|
||||
if (readme > 0) optionstr[readme - 1] = "Show ReadMe";
|
||||
|
||||
|
||||
int user_select = ShowSelectPrompt(n_opt, optionstr, promptstr);
|
||||
if (user_select == sdformat) { // format SD card
|
||||
bool sd_state = CheckSDMountState();
|
||||
@ -2178,27 +2178,27 @@ u32 HomeMoreMenu(char* current_path) {
|
||||
MemToCViewer(README_md, README_md_size, "GodMode9 ReadMe Table of Contents");
|
||||
return 0;
|
||||
} else return 1;
|
||||
|
||||
|
||||
return HomeMoreMenu(current_path);
|
||||
}
|
||||
|
||||
u32 GodMode(int entrypoint) {
|
||||
const u32 quick_stp = (MAIN_SCREEN == TOP_SCREEN) ? 20 : 19;
|
||||
u32 exit_mode = GODMODE_EXIT_POWEROFF;
|
||||
|
||||
|
||||
char current_path[256] = { 0x00 };
|
||||
u32 cursor = 0;
|
||||
u32 scroll = 0;
|
||||
|
||||
|
||||
int mark_next = -1;
|
||||
u32 last_write_perm = GetWritePermissions();
|
||||
u32 last_clipboard_size = 0;
|
||||
|
||||
|
||||
bool bootloader = IS_UNLOCKED && (entrypoint == ENTRY_NANDBOOT);
|
||||
bool bootmenu = bootloader && (BOOTMENU_KEY != BUTTON_START) && CheckButton(BOOTMENU_KEY);
|
||||
bool godmode9 = !bootloader;
|
||||
|
||||
|
||||
|
||||
// FIRM from FCRAM handling
|
||||
FirmHeader* firm_in_mem = (FirmHeader*) __FIRMTMP_ADDR; // should be safe here
|
||||
if (bootloader) { // check for FIRM in FCRAM, but prevent bootloops
|
||||
@ -2210,27 +2210,27 @@ u32 GodMode(int entrypoint) {
|
||||
memcpy(addr, "NOPE", 4); // to prevent bootloops
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// get mode string for splash screen
|
||||
const char* disp_mode = NULL;
|
||||
if (bootloader) disp_mode = "bootloader mode\nR+LEFT for menu";
|
||||
else if (!IS_UNLOCKED && (entrypoint == ENTRY_NANDBOOT)) disp_mode = "oldloader mode";
|
||||
else if (entrypoint == ENTRY_NTRBOOT) disp_mode = "ntrboot mode";
|
||||
else if (entrypoint == ENTRY_UNKNOWN) disp_mode = "unknown mode";
|
||||
|
||||
|
||||
bool show_splash = true;
|
||||
#ifdef SALTMODE
|
||||
show_splash = !bootloader;
|
||||
#endif
|
||||
|
||||
|
||||
// init font
|
||||
if (!SetFontFromPbm(NULL, 0)) return exit_mode;
|
||||
|
||||
|
||||
// show splash screen (if enabled)
|
||||
ClearScreenF(true, true, COLOR_STD_BG);
|
||||
if (show_splash) SplashInit(disp_mode);
|
||||
u64 timer = timer_start(); // for splash delay
|
||||
|
||||
|
||||
InitSDCardFS();
|
||||
AutoEmuNandBase(true);
|
||||
InitNandCrypto(true); // (entrypoint != ENTRY_B9S);
|
||||
@ -2252,7 +2252,7 @@ u32 GodMode(int entrypoint) {
|
||||
free(pbm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check for embedded essential backup
|
||||
if (((entrypoint == ENTRY_NANDBOOT) || (entrypoint == ENTRY_B9S)) &&
|
||||
!PathExist("S:/essential.exefs") && CheckGenuineNandNcsd() &&
|
||||
@ -2263,7 +2263,7 @@ u32 GodMode(int entrypoint) {
|
||||
ShowPrompt(false, "Backup embedded in SysNAND\nand written to " OUTPUT_PATH ".");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check internal clock
|
||||
if (IS_UNLOCKED) { // we could actually do this on any entrypoint
|
||||
DsTime dstime;
|
||||
@ -2277,12 +2277,12 @@ u32 GodMode(int entrypoint) {
|
||||
ShowPrompt(false, "New RTC date&time is:\n%s\n \nHint: HOMEMENU time needs\nmanual adjustment after\nsetting the RTC.", timestr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check aeskeydb.bin / key state
|
||||
if ((entrypoint != ENTRY_B9S) && (CheckRecommendedKeyDb(NULL) != 0)) {
|
||||
ShowPrompt(false, "WARNING:\nNot running from a boot9strap\ncompatible entrypoint. Not\neverything may work as expected.\n \nProvide the recommended\naeskeydb.bin file to make this\nwarning go away.");
|
||||
}
|
||||
|
||||
|
||||
#if defined(SALTMODE)
|
||||
show_splash = bootmenu = (bootloader && CheckButton(BOOTMENU_KEY));
|
||||
if (show_splash) SplashInit("saltmode");
|
||||
@ -2317,7 +2317,7 @@ u32 GodMode(int entrypoint) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// bootloader handler
|
||||
if (bootloader) {
|
||||
const char* bootfirm_paths[] = { BOOTFIRM_PATHS };
|
||||
@ -2331,7 +2331,7 @@ u32 GodMode(int entrypoint) {
|
||||
ShowPrompt(false, "No bootable FIRM found.\nNow resuming GodMode9...");
|
||||
godmode9 = true;
|
||||
}
|
||||
|
||||
|
||||
if (godmode9) {
|
||||
current_dir = (DirStruct*) malloc(sizeof(DirStruct));
|
||||
clipboard = (DirStruct*) malloc(sizeof(DirStruct));
|
||||
@ -2340,13 +2340,13 @@ u32 GodMode(int entrypoint) {
|
||||
ShowPrompt(false, "Out of memory."); // just to be safe
|
||||
return exit_mode;
|
||||
}
|
||||
|
||||
|
||||
GetDirContents(current_dir, "");
|
||||
clipboard->n_entries = 0;
|
||||
memset(panedata, 0x00, N_PANES * sizeof(PaneData));
|
||||
ClearScreenF(true, true, COLOR_STD_BG); // clear splash
|
||||
}
|
||||
|
||||
|
||||
PaneData* pane = panedata;
|
||||
while (godmode9) { // this is the main loop
|
||||
// basic sanity checking
|
||||
@ -2364,7 +2364,7 @@ u32 GodMode(int entrypoint) {
|
||||
}
|
||||
if (cursor >= current_dir->n_entries) // cursor beyond allowed range
|
||||
cursor = current_dir->n_entries - 1;
|
||||
|
||||
|
||||
int curr_drvtype = DriveType(current_path);
|
||||
DirEntry* curr_entry = &(current_dir->entry[cursor]);
|
||||
if ((mark_next >= 0) && (curr_entry->type != T_DOTDOT)) {
|
||||
@ -2374,7 +2374,7 @@ u32 GodMode(int entrypoint) {
|
||||
DrawDirContents(current_dir, cursor, &scroll);
|
||||
DrawUserInterface(current_path, curr_entry, N_PANES ? pane - panedata + 1 : 0);
|
||||
DrawTopBar(current_path);
|
||||
|
||||
|
||||
// check write permissions
|
||||
if (~last_write_perm & GetWritePermissions()) {
|
||||
if (ShowPrompt(true, "Write permissions were changed.\nRelock them?")) SetWritePermissions(last_write_perm, false);
|
||||
@ -2385,7 +2385,7 @@ u32 GodMode(int entrypoint) {
|
||||
// handle user input
|
||||
u32 pad_state = InputWait(3);
|
||||
bool switched = (pad_state & BUTTON_R1);
|
||||
|
||||
|
||||
// basic navigation commands
|
||||
if ((pad_state & BUTTON_A) && (curr_entry->type != T_FILE) && (curr_entry->type != T_DOTDOT)) { // for dirs
|
||||
if (switched && !(DriveType(curr_entry->path) & DRV_SEARCH)) { // search directory
|
||||
@ -2444,8 +2444,8 @@ u32 GodMode(int entrypoint) {
|
||||
current_path[255] = '\0';
|
||||
if (user_select == 2) {
|
||||
char* last_slash = strrchr(current_path, '/');
|
||||
if (last_slash) *last_slash = '\0';
|
||||
}
|
||||
if (last_slash) *last_slash = '\0';
|
||||
}
|
||||
GetDirContents(current_dir, current_path);
|
||||
if (*current_path && (current_dir->n_entries > 1)) {
|
||||
cursor = 1;
|
||||
@ -2465,7 +2465,7 @@ u32 GodMode(int entrypoint) {
|
||||
char old_path[256];
|
||||
char* last_slash = strrchr(current_path, '/');
|
||||
strncpy(old_path, current_path, 256);
|
||||
if (last_slash) *last_slash = '\0';
|
||||
if (last_slash) *last_slash = '\0';
|
||||
else *current_path = '\0';
|
||||
GetDirContents(current_dir, current_path);
|
||||
if (*old_path && current_dir->n_entries) {
|
||||
@ -2545,7 +2545,7 @@ u32 GodMode(int entrypoint) {
|
||||
} else if (!switched) { // standard unswitched command set
|
||||
if ((curr_drvtype & DRV_VIRTUAL) && (pad_state & BUTTON_X) && (*current_path != 'T')) {
|
||||
ShowPrompt(false, "Not allowed in virtual path");
|
||||
} else if (pad_state & BUTTON_X) { // delete a file
|
||||
} else if (pad_state & BUTTON_X) { // delete a file
|
||||
u32 n_marked = 0;
|
||||
if (curr_entry->marked) {
|
||||
for (u32 c = 0; c < current_dir->n_entries; c++)
|
||||
@ -2612,16 +2612,16 @@ u32 GodMode(int entrypoint) {
|
||||
TruncateString(namestr, clipboard->entry[c].name, 36, 12);
|
||||
flags &= ~ASK_ALL;
|
||||
if (c < clipboard->n_entries - 1) flags |= ASK_ALL;
|
||||
if ((user_select == 1) && !PathCopy(current_path, clipboard->entry[c].path, &flags)) {
|
||||
if ((user_select == 1) && !PathCopy(current_path, clipboard->entry[c].path, &flags)) {
|
||||
if (c + 1 < clipboard->n_entries) {
|
||||
if (!ShowPrompt(true, "Failed copying path:\n%s\nProcess remaining?", namestr)) break;
|
||||
} else ShowPrompt(false, "Failed copying path:\n%s", namestr);
|
||||
} else if ((user_select == 2) && !PathMove(current_path, clipboard->entry[c].path, &flags)) {
|
||||
} else if ((user_select == 2) && !PathMove(current_path, clipboard->entry[c].path, &flags)) {
|
||||
if (c + 1 < clipboard->n_entries) {
|
||||
if (!ShowPrompt(true, "Failed moving path:\n%s\nProcess remaining?", namestr)) break;
|
||||
} else ShowPrompt(false, "Failed moving path:\n%s", namestr);
|
||||
}
|
||||
}
|
||||
}
|
||||
clipboard->n_entries = 0;
|
||||
GetDirContents(current_dir, current_path);
|
||||
}
|
||||
@ -2670,7 +2670,7 @@ u32 GodMode(int entrypoint) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (pad_state & BUTTON_START) {
|
||||
exit_mode = (switched || (pad_state & BUTTON_LEFT)) ? GODMODE_EXIT_POWEROFF : GODMODE_EXIT_REBOOT;
|
||||
break;
|
||||
@ -2690,7 +2690,7 @@ u32 GodMode(int entrypoint) {
|
||||
if (scripts > 0) optionstr[scripts - 1] = "Scripts...";
|
||||
if (payloads > 0) optionstr[payloads - 1] = "Payloads...";
|
||||
if (more > 0) optionstr[more - 1] = "More...";
|
||||
|
||||
|
||||
int user_select = 0;
|
||||
while ((user_select = ShowSelectPrompt(n_opt, optionstr, "%s button pressed.\nSelect action:", buttonstr)) &&
|
||||
(user_select != poweroff) && (user_select != reboot)) {
|
||||
@ -2715,11 +2715,11 @@ u32 GodMode(int entrypoint) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (user_select == poweroff) {
|
||||
|
||||
if (user_select == poweroff) {
|
||||
exit_mode = GODMODE_EXIT_POWEROFF;
|
||||
break;
|
||||
} else if (user_select == reboot) {
|
||||
} else if (user_select == reboot) {
|
||||
exit_mode = GODMODE_EXIT_REBOOT;
|
||||
break;
|
||||
}
|
||||
@ -2746,15 +2746,15 @@ u32 GodMode(int entrypoint) {
|
||||
GetDirContents(current_dir, current_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
DeinitExtFS();
|
||||
DeinitSDCardFS();
|
||||
|
||||
|
||||
if (current_dir) free(current_dir);
|
||||
if (clipboard) free(clipboard);
|
||||
if (panedata) free(panedata);
|
||||
|
||||
|
||||
return exit_mode;
|
||||
}
|
||||
|
||||
@ -2764,7 +2764,7 @@ u32 ScriptRunner(int entrypoint) {
|
||||
if (!SetFontFromPbm(NULL, 0)) return GODMODE_EXIT_POWEROFF;
|
||||
SplashInit("scriptrunner mode");
|
||||
u64 timer = timer_start();
|
||||
|
||||
|
||||
InitSDCardFS();
|
||||
AutoEmuNandBase(true);
|
||||
InitNandCrypto(entrypoint != ENTRY_B9S);
|
||||
@ -2776,14 +2776,14 @@ u32 ScriptRunner(int entrypoint) {
|
||||
s32 brightness = -1;
|
||||
if (LoadSupportFile("gm9bright.cfg", &brightness, 0x4))
|
||||
SetScreenBrightness(brightness);
|
||||
|
||||
|
||||
while (CheckButton(BOOTPAUSE_KEY)); // don't continue while these keys are held
|
||||
while (timer_msec( timer ) < 500); // show splash for at least 0.5 sec
|
||||
|
||||
// you didn't really install a scriptrunner to NAND, did you?
|
||||
if (IS_UNLOCKED && (entrypoint == ENTRY_NANDBOOT))
|
||||
BootFirmHandler("0:/iderped.firm", false, false);
|
||||
|
||||
|
||||
if (PathExist("V:/" VRAM0_AUTORUN_GM9)) {
|
||||
ClearScreenF(true, true, COLOR_STD_BG); // clear splash
|
||||
ExecuteGM9Script("V:/" VRAM0_AUTORUN_GM9);
|
||||
@ -2792,11 +2792,11 @@ u32 ScriptRunner(int entrypoint) {
|
||||
if (FileSelector(loadpath, FLAVOR " scripts menu.\nSelect script:", "V:/" VRAM0_SCRIPTS, "*.gm9", HIDE_EXT, false))
|
||||
ExecuteGM9Script(loadpath);
|
||||
} else ShowPrompt(false, "Compiled as script autorunner\nbut no script provided.\n \nDerp!");
|
||||
|
||||
|
||||
// deinit
|
||||
DeinitExtFS();
|
||||
DeinitSDCardFS();
|
||||
|
||||
|
||||
return GODMODE_EXIT_REBOOT;
|
||||
}
|
||||
#endif
|
||||
|
@ -23,7 +23,7 @@ typedef struct {
|
||||
// see: http://3dbrew.org/wiki/Nand/private/movable.sed
|
||||
typedef struct {
|
||||
u8 magic[0x4]; // "SEED"
|
||||
u8 indicator[0x4]; // uninitialized all zero, otherwise u8[1] nonzero
|
||||
u8 indicator[0x4]; // uninitialized all zero, otherwise u8[1] nonzero
|
||||
LocalFriendCodeSeed codeseed_data;
|
||||
u8 keyy_high[8];
|
||||
u8 unknown[0x10];
|
||||
|
@ -33,7 +33,7 @@ static const u8 ALIGN(4) slot0x05KeyY_sha256[0x20] = { // hash for slot0x05KeyY
|
||||
};
|
||||
|
||||
static const u8 ALIGN(4) slot0x11Key95_sha256[0x20] = { // slot0x11Key95 hash (first 16 byte of sector0x96)
|
||||
0xBA, 0xC1, 0x40, 0x9C, 0x6E, 0xE4, 0x1F, 0x04, 0xAA, 0xC4, 0xE2, 0x09, 0x5C, 0xE9, 0x4F, 0x78,
|
||||
0xBA, 0xC1, 0x40, 0x9C, 0x6E, 0xE4, 0x1F, 0x04, 0xAA, 0xC4, 0xE2, 0x09, 0x5C, 0xE9, 0x4F, 0x78,
|
||||
0x6C, 0x78, 0x5F, 0xAC, 0xEC, 0x7E, 0xC0, 0x11, 0x26, 0x9D, 0x4E, 0x47, 0xB3, 0x64, 0xC4, 0xA5
|
||||
};
|
||||
|
||||
@ -78,12 +78,12 @@ bool GetOtp0x90(void* otp0x90, u32 len)
|
||||
cbc_encrypt(otp0x90, otp0x90, len / 0x10, AES_CNT_TITLEKEY_ENCRYPT_MODE, otp_iv);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InitNandCrypto(bool init_full)
|
||||
{
|
||||
{
|
||||
// part #0: KeyX / KeyY for secret sector 0x96
|
||||
if (IS_UNLOCKED) { // if OTP is unlocked
|
||||
// see: https://www.3dbrew.org/wiki/OTP_Registers
|
||||
@ -95,11 +95,11 @@ bool InitNandCrypto(bool init_full)
|
||||
if (GetOtp0x90(otp0x90, 0x90))
|
||||
sha_quick(OtpSha256, otp0x90, 0x90, SHA256_MODE);
|
||||
}
|
||||
|
||||
|
||||
// part #1: Get NAND CID, set up TWL/CTR counter
|
||||
u32 NandCid[4];
|
||||
u8 shasum[32];
|
||||
|
||||
|
||||
sdmmc_sdcard_init();
|
||||
sdmmc_get_cid(1, NandCid);
|
||||
sha_quick(shasum, (u8*) NandCid, 16, SHA256_MODE);
|
||||
@ -107,7 +107,7 @@ bool InitNandCrypto(bool init_full)
|
||||
sha_quick(shasum, (u8*) NandCid, 16, SHA1_MODE);
|
||||
for(u32 i = 0; i < 16; i++) // little endian and reversed order
|
||||
TwlNandCtr[i] = shasum[15-i];
|
||||
|
||||
|
||||
// part #2: TWL KEY (if not already set up)
|
||||
// see: https://www.3dbrew.org/wiki/Memory_layout#ARM9_ITCM
|
||||
if (GetNandPartitionInfo(NULL, NP_TYPE_FAT, NP_SUBTYPE_TWL, 0, NAND_SYSNAND) != 0) {
|
||||
@ -118,11 +118,11 @@ bool InitNandCrypto(bool init_full)
|
||||
u64 ALIGN(32) otp0x10[2];
|
||||
if (GetOtp0x90(otp0x10, 0x10)) TwlCustId = *otp0x10;
|
||||
}
|
||||
|
||||
|
||||
if (TwlCustId) { // give up if TwlCustId not found
|
||||
u32 ALIGN(32) TwlKey0x03Y[4];
|
||||
u32 ALIGN(32) TwlKey0x03X[4];
|
||||
|
||||
|
||||
if (IS_DEVKIT) {
|
||||
TwlKey0x03X[1] = 0xEE7A4B1E;
|
||||
TwlKey0x03X[2] = 0xAF42C08B;
|
||||
@ -132,20 +132,20 @@ bool InitNandCrypto(bool init_full)
|
||||
TwlKey0x03X[2] = *(vu32*)0x01FFD3AC; // "ENDO"
|
||||
memcpy(TwlKey0x03Y, (u8*) 0x01FFD3C8, 16);
|
||||
}
|
||||
|
||||
|
||||
TwlKey0x03X[0] = (u32) (TwlCustId>>0);
|
||||
TwlKey0x03X[3] = (u32) (TwlCustId>>32);
|
||||
TwlKey0x03Y[3] = 0xE1A00005;
|
||||
|
||||
|
||||
setup_aeskeyX(0x03, TwlKey0x03X);
|
||||
setup_aeskeyY(0x03, TwlKey0x03Y);
|
||||
use_aeskey(0x03);
|
||||
|
||||
|
||||
if (init_full) { // full init
|
||||
vu32 *RegKey0x01X = ®_AESKEY0123[((0x30u * 0x01) + 0x10u)/4u];
|
||||
RegKey0x01X[2] = (u32) (TwlCustId>>32);
|
||||
RegKey0x01X[3] = (u32) (TwlCustId>>0);
|
||||
|
||||
|
||||
setup_aeskeyX(0x02, (u8*)0x01FFD398);
|
||||
if (IS_DEVKIT) {
|
||||
u32 ALIGN(32) TwlKey0x02Y[4];
|
||||
@ -153,24 +153,24 @@ bool InitNandCrypto(bool init_full)
|
||||
setup_aeskeyY(0x02, TwlKey0x02Y);
|
||||
} else setup_aeskeyY(0x02, (u8*)0x01FFD220);
|
||||
use_aeskey(0x02);
|
||||
|
||||
|
||||
if (IS_UNLOCKED)
|
||||
(*(vu64*)0x10012100) = TwlCustId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// part #3: CTRNAND N3DS KEY (if not set up)
|
||||
if (GetNandPartitionInfo(NULL, NP_TYPE_FAT, NP_SUBTYPE_CTR, 0, NAND_SYSNAND) != 0)
|
||||
LoadKeyFromFile(slot0x05KeyY, 0x05, 'Y', NULL);
|
||||
|
||||
|
||||
// part #4: AGBSAVE CMAC KEY (set up on unlocked systems)
|
||||
if (init_full && IS_UNLOCKED)
|
||||
LoadKeyFromFile(NULL, 0x24, 'Y', NULL);
|
||||
|
||||
|
||||
// part #5: FULL INIT
|
||||
if (init_full) InitKeyDb(NULL);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -179,11 +179,11 @@ bool CheckSlot0x05Crypto(void)
|
||||
// step #1 - check the slot0x05KeyY SHA-256
|
||||
if (sha_cmp(slot0x05KeyY_sha256, slot0x05KeyY, 16, SHA256_MODE) == 0)
|
||||
return true;
|
||||
|
||||
|
||||
// step #2 - check actual presence of CTRNAND FAT
|
||||
if (GetNandPartitionInfo(NULL, NP_TYPE_STD, NP_SUBTYPE_CTR_N, 0, NAND_SYSNAND) == 0)
|
||||
return true;
|
||||
|
||||
|
||||
// failed if we arrive here
|
||||
return false;
|
||||
}
|
||||
@ -206,14 +206,14 @@ bool CheckGenuineNandNcsd(void)
|
||||
};
|
||||
u8 ALIGN(4) gen_n3ds_hash[0x20] = {
|
||||
0x49, 0xB7, 0x4A, 0xF1, 0xFD, 0xB7, 0xCF, 0x5B, 0x76, 0x8F, 0xA2, 0x94, 0x0D, 0xB2, 0xB3, 0xE2,
|
||||
0xA4, 0xBD, 0x25, 0x03, 0x06, 0x03, 0x47, 0x0B, 0x24, 0x5A, 0x86, 0x6A, 0x43, 0x60, 0xBC, 0x84,
|
||||
0xA4, 0xBD, 0x25, 0x03, 0x06, 0x03, 0x47, 0x0B, 0x24, 0x5A, 0x86, 0x6A, 0x43, 0x60, 0xBC, 0x84,
|
||||
};
|
||||
|
||||
u8 ALIGN(4) gen_hdr[0x100];
|
||||
if ((ReadNandBytes(gen_hdr, 0x100, 0x100, 0xFF, NAND_SYSNAND) != 0) ||
|
||||
(ReadNandBytes(gen_hdr + 0xBE, 0x1BE, 0x42, 0x03, NAND_SYSNAND) != 0))
|
||||
return false;
|
||||
|
||||
|
||||
return (sha_cmp((IS_O3DS) ? gen_o3ds_hash : gen_n3ds_hash, gen_hdr, 0x100, SHA256_MODE) == 0);
|
||||
}
|
||||
|
||||
@ -226,7 +226,7 @@ void CryptNand(void* buffer, u32 sector, u32 count, u32 keyslot)
|
||||
// copy NAND CTR and increment it
|
||||
memcpy(ctr, (keyslot != 0x03) ? CtrNandCtr : TwlNandCtr, 16); // hacky again
|
||||
add_ctr(ctr, sector * (0x200 / 0x10));
|
||||
|
||||
|
||||
// decrypt the data
|
||||
use_aeskey(keyslot);
|
||||
ctr_decrypt((void*) buffer, (void*) buffer, blocks, mode, ctr);
|
||||
@ -235,11 +235,11 @@ void CryptNand(void* buffer, u32 sector, u32 count, u32 keyslot)
|
||||
void CryptSector0x96(void* buffer, bool encrypt)
|
||||
{
|
||||
u32 mode = encrypt ? AES_CNT_ECB_ENCRYPT_MODE : AES_CNT_ECB_DECRYPT_MODE;
|
||||
|
||||
|
||||
// setup the key
|
||||
setup_aeskeyX(0x11, OtpSha256);
|
||||
setup_aeskeyY(0x11, OtpSha256 + 16);
|
||||
|
||||
|
||||
// decrypt the sector
|
||||
use_aeskey(0x11);
|
||||
ecb_decrypt((void*) buffer, (void*) buffer, 0x200 / AES_BLOCK_SIZE, mode);
|
||||
@ -247,7 +247,7 @@ void CryptSector0x96(void* buffer, bool encrypt)
|
||||
|
||||
int ReadNandBytes(void* buffer, u64 offset, u64 count, u32 keyslot, u32 nand_src)
|
||||
{
|
||||
if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case
|
||||
if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case
|
||||
// simple wrapper function for ReadNandSectors(...)
|
||||
return ReadNandSectors(buffer, offset / 0x200, count / 0x200, keyslot, nand_src);
|
||||
} else { // misaligned data -> -___-
|
||||
@ -280,7 +280,7 @@ int ReadNandBytes(void* buffer, u64 offset, u64 count, u32 keyslot, u32 nand_src
|
||||
|
||||
int WriteNandBytes(const void* buffer, u64 offset, u64 count, u32 keyslot, u32 nand_dst)
|
||||
{
|
||||
if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case
|
||||
if (!(offset % 0x200) && !(count % 0x200)) { // aligned data -> simple case
|
||||
// simple wrapper function for WriteNandSectors(...)
|
||||
return WriteNandSectors(buffer, offset / 0x200, count / 0x200, keyslot, nand_dst);
|
||||
} else { // misaligned data -> -___-
|
||||
@ -316,7 +316,7 @@ int WriteNandBytes(const void* buffer, u64 offset, u64 count, u32 keyslot, u32 n
|
||||
}
|
||||
|
||||
int ReadNandSectors(void* buffer, u32 sector, u32 count, u32 keyslot, u32 nand_src)
|
||||
{
|
||||
{
|
||||
u8* buffer8 = (u8*) buffer;
|
||||
if (!count) return 0; // <--- just to be safe
|
||||
if (nand_src == NAND_EMUNAND) { // EmuNAND
|
||||
@ -335,7 +335,7 @@ int ReadNandSectors(void* buffer, u32 sector, u32 count, u32 keyslot, u32 nand_s
|
||||
if (errorcode) return errorcode;
|
||||
} else if (nand_src == NAND_SYSNAND) { // SysNAND
|
||||
int errorcode = sdmmc_nand_readsectors(sector, count, buffer8);
|
||||
if (errorcode) return errorcode;
|
||||
if (errorcode) return errorcode;
|
||||
} else if (nand_src == NAND_ZERONAND) { // zero NAND (good for XORpads)
|
||||
memset(buffer8, 0, count * 0x200);
|
||||
} else {
|
||||
@ -343,7 +343,7 @@ int ReadNandSectors(void* buffer, u32 sector, u32 count, u32 keyslot, u32 nand_s
|
||||
}
|
||||
if ((keyslot == 0x11) && (sector == SECTOR_SECRET)) CryptSector0x96(buffer8, false);
|
||||
else if (keyslot < 0x40) CryptNand(buffer8, sector, count, keyslot);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -353,7 +353,7 @@ int WriteNandSectors(const void* buffer, u32 sector, u32 count, u32 keyslot, u32
|
||||
void* nand_buffer = (void*) malloc(min(STD_BUFFER_SIZE, count * 0x200));
|
||||
if (!nand_buffer) return -1;
|
||||
int errorcode = 0;
|
||||
|
||||
|
||||
for (u32 s = 0; s < count; s += (STD_BUFFER_SIZE / 0x200)) {
|
||||
u32 pcount = min((STD_BUFFER_SIZE/0x200), (count - s));
|
||||
memcpy(nand_buffer, ((u8*) buffer) + (s*0x200), pcount * 0x200);
|
||||
@ -372,7 +372,7 @@ int WriteNandSectors(const void* buffer, u32 sector, u32 count, u32 keyslot, u32
|
||||
errorcode = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
free(nand_buffer);
|
||||
return errorcode;
|
||||
}
|
||||
@ -390,7 +390,7 @@ u32 ValidateNandNcsdHeader(NandNcsdHeader* header)
|
||||
if ((memcmp(header->magic, "NCSD", 4) != 0) || // check magic number
|
||||
(memcmp(header->partitions_fs_type, zeroes, 8) == 0) || header->mediaId) // prevent detection of cart NCSD images
|
||||
return 1;
|
||||
|
||||
|
||||
u32 data_units = 0;
|
||||
u32 firm_count = 0;
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
@ -406,7 +406,7 @@ u32 ValidateNandNcsdHeader(NandNcsdHeader* header)
|
||||
}
|
||||
if (data_units > header->size) return 1;
|
||||
if (!firm_count) return 1; // at least one firm is required
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -417,7 +417,7 @@ u32 GetNandNcsdMinSizeSectors(NandNcsdHeader* ncsd)
|
||||
u32 prt_end = ncsd->partitions[prt_idx].offset + ncsd->partitions[prt_idx].size;
|
||||
if (prt_end > nand_minsize) nand_minsize = prt_end;
|
||||
}
|
||||
|
||||
|
||||
return nand_minsize;
|
||||
}
|
||||
|
||||
@ -426,7 +426,7 @@ u32 GetNandMinSizeSectors(u32 nand_src)
|
||||
NandNcsdHeader ncsd;
|
||||
if ((ReadNandSectors((u8*) &ncsd, 0, 1, 0xFF, nand_src) != 0) ||
|
||||
(ValidateNandNcsdHeader(&ncsd) != 0)) return 0;
|
||||
|
||||
|
||||
return GetNandNcsdMinSizeSectors(&ncsd);
|
||||
}
|
||||
|
||||
@ -434,7 +434,7 @@ u32 GetNandSizeSectors(u32 nand_src)
|
||||
{
|
||||
u32 sysnand_sectors = getMMCDevice(0)->total_size;
|
||||
if (nand_src == NAND_SYSNAND) return sysnand_sectors; // for SysNAND
|
||||
|
||||
|
||||
u32 min_sectors = GetNandMinSizeSectors(nand_src);
|
||||
if (nand_src == NAND_EMUNAND) { // for EmuNAND
|
||||
u32 partition_offset = GetPartitionOffsetSector("0:");
|
||||
@ -447,7 +447,7 @@ u32 GetNandSizeSectors(u32 nand_src)
|
||||
u32 img_sectors = (GetMountState() & IMG_NAND) ? GetMountSize() / 0x200 : 0;
|
||||
return (img_sectors >= min_sectors) ? img_sectors : 0;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -456,14 +456,14 @@ u32 GetNandNcsdPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32
|
||||
// safety / set keyslot
|
||||
if ((type == NP_TYPE_FAT) || (type > NP_TYPE_BONUS) || (subtype > NP_SUBTYPE_CTR_N)) return 1;
|
||||
info->keyslot = np_keyslots[type][subtype];
|
||||
|
||||
|
||||
// full (minimum) NAND "partition"
|
||||
if (type == NP_TYPE_NONE) {
|
||||
info->sector = 0x00;
|
||||
info->count = GetNandNcsdMinSizeSectors(ncsd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// special, custom partition types, not in NCSD
|
||||
if (type >= NP_TYPE_NCSD) {
|
||||
if (type == NP_TYPE_NCSD) {
|
||||
@ -484,7 +484,7 @@ u32 GetNandNcsdPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32
|
||||
} else return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
u32 prt_idx = 8;
|
||||
for (prt_idx = 0; prt_idx < 8; prt_idx++) {
|
||||
if ((ncsd->partitions_fs_type[prt_idx] != type) ||
|
||||
@ -492,11 +492,11 @@ u32 GetNandNcsdPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32
|
||||
if (index == 0) break;
|
||||
index--;
|
||||
}
|
||||
|
||||
|
||||
if (prt_idx >= 8) return 1; // not found
|
||||
info->sector = ncsd->partitions[prt_idx].offset;
|
||||
info->count = ncsd->partitions[prt_idx].size;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -546,7 +546,7 @@ u32 AutoEmuNandBase(bool reset)
|
||||
if (!reset) {
|
||||
u32 last_valid = emunand_base_sector;
|
||||
u32 emunand_min_sectors = GetNandMinSizeSectors(NAND_EMUNAND);
|
||||
|
||||
|
||||
// legacy type multiNAND
|
||||
u32 legacy_sectors = (getMMCDevice(0)->total_size > 0x200000) ? 0x400000 : 0x200000;
|
||||
emunand_base_sector += legacy_sectors - (emunand_base_sector % legacy_sectors);
|
||||
@ -555,7 +555,7 @@ u32 AutoEmuNandBase(bool reset)
|
||||
emunand_base_sector++;
|
||||
if (GetNandSizeSectors(NAND_EMUNAND) && (GetNandPartitionInfo(NULL, NP_TYPE_NCSD, NP_SUBTYPE_CTR, 0, NAND_EMUNAND) == 0))
|
||||
return emunand_base_sector; // RedNAND type EmuNAND
|
||||
|
||||
|
||||
// compact type multiNAND
|
||||
if (emunand_min_sectors && (last_valid % 0x2000 <= 1)) {
|
||||
u32 compact_sectors = align(emunand_min_sectors + 1, 0x2000);
|
||||
@ -564,7 +564,7 @@ u32 AutoEmuNandBase(bool reset)
|
||||
return emunand_base_sector;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
emunand_base_sector = 0x000001; // RedNAND type EmuNAND, default
|
||||
if (GetNandPartitionInfo(NULL, NP_TYPE_NCSD, NP_SUBTYPE_CTR, 0, NAND_EMUNAND) != 0) {
|
||||
emunand_base_sector = 0x000000; // GW type EmuNAND
|
||||
@ -573,7 +573,7 @@ u32 AutoEmuNandBase(bool reset)
|
||||
// still nothing found? revert to default (RedNAND offset)
|
||||
emunand_base_sector = 0x000001;
|
||||
}
|
||||
|
||||
|
||||
return emunand_base_sector;
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
/*
|
||||
/*
|
||||
* QR Code generator library (C)
|
||||
*
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
@ -128,12 +128,12 @@ static const int PENALTY_N4 = 10;
|
||||
// Public function - see documentation comment in header file.
|
||||
bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[],
|
||||
enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) {
|
||||
|
||||
|
||||
size_t textLen = strlen(text);
|
||||
if (textLen == 0)
|
||||
return qrcodegen_encodeSegmentsAdvanced(NULL, 0, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode);
|
||||
size_t bufLen = qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion);
|
||||
|
||||
|
||||
struct qrcodegen_Segment seg;
|
||||
if (qrcodegen_isNumeric(text)) {
|
||||
if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_NUMERIC, textLen) > bufLen)
|
||||
@ -156,7 +156,7 @@ bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode
|
||||
seg.data = tempBuffer;
|
||||
}
|
||||
return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode);
|
||||
|
||||
|
||||
fail:
|
||||
qrcode[0] = 0; // Set size to invalid value for safety
|
||||
return false;
|
||||
@ -166,7 +166,7 @@ fail:
|
||||
// Public function - see documentation comment in header file.
|
||||
bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[],
|
||||
enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) {
|
||||
|
||||
|
||||
struct qrcodegen_Segment seg;
|
||||
seg.mode = qrcodegen_Mode_BYTE;
|
||||
seg.bitLength = calcSegmentBitLength(seg.mode, dataLen);
|
||||
@ -204,7 +204,7 @@ testable void appendErrorCorrection(uint8_t data[], int version, enum qrcodegen_
|
||||
int dataLen = rawCodewords - blockEccLen * numBlocks;
|
||||
int numShortBlocks = numBlocks - rawCodewords % numBlocks;
|
||||
int shortBlockDataLen = rawCodewords / numBlocks - blockEccLen;
|
||||
|
||||
|
||||
// Split data into blocks and append ECC after all data
|
||||
uint8_t generator[30];
|
||||
calcReedSolomonGenerator(blockEccLen, generator);
|
||||
@ -216,7 +216,7 @@ testable void appendErrorCorrection(uint8_t data[], int version, enum qrcodegen_
|
||||
j += blockEccLen;
|
||||
k += blockLen;
|
||||
}
|
||||
|
||||
|
||||
// Interleave (not concatenate) the bytes from every block into a single sequence
|
||||
for (int i = 0, k = 0; i < numBlocks; i++) {
|
||||
for (int j = 0, l = i; j < shortBlockDataLen; j++, k++, l += numBlocks)
|
||||
@ -268,7 +268,7 @@ testable void calcReedSolomonGenerator(int degree, uint8_t result[]) {
|
||||
assert(1 <= degree && degree <= 30);
|
||||
memset(result, 0, degree * sizeof(result[0]));
|
||||
result[degree - 1] = 1;
|
||||
|
||||
|
||||
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
|
||||
// drop the highest term, and store the rest of the coefficients in order of descending powers.
|
||||
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
|
||||
@ -289,7 +289,7 @@ testable void calcReedSolomonGenerator(int degree, uint8_t result[]) {
|
||||
// polynomials are in big endian and the generator has an implicit leading 1 term, storing the result in result[0 : degree].
|
||||
testable void calcReedSolomonRemainder(const uint8_t data[], int dataLen,
|
||||
const uint8_t generator[], int degree, uint8_t result[]) {
|
||||
|
||||
|
||||
// Perform polynomial division
|
||||
assert(1 <= degree && degree <= 30);
|
||||
memset(result, 0, degree * sizeof(result[0]));
|
||||
@ -326,16 +326,16 @@ testable void initializeFunctionModules(int version, uint8_t qrcode[]) {
|
||||
int qrsize = version * 4 + 17;
|
||||
memset(qrcode, 0, ((qrsize * qrsize + 7) / 8 + 1) * sizeof(qrcode[0]));
|
||||
qrcode[0] = (uint8_t)qrsize;
|
||||
|
||||
|
||||
// Fill horizontal and vertical timing patterns
|
||||
fillRectangle(6, 0, 1, qrsize, qrcode);
|
||||
fillRectangle(0, 6, qrsize, 1, qrcode);
|
||||
|
||||
|
||||
// Fill 3 finder patterns (all corners except bottom right) and format bits
|
||||
fillRectangle(0, 0, 9, 9, qrcode);
|
||||
fillRectangle(qrsize - 8, 0, 8, 9, qrcode);
|
||||
fillRectangle(0, qrsize - 8, 9, 8, qrcode);
|
||||
|
||||
|
||||
// Fill numerous alignment patterns
|
||||
uint8_t alignPatPos[7] = {0};
|
||||
int numAlign = getAlignmentPatternPositions(version, alignPatPos);
|
||||
@ -347,7 +347,7 @@ testable void initializeFunctionModules(int version, uint8_t qrcode[]) {
|
||||
fillRectangle(alignPatPos[i] - 2, alignPatPos[j] - 2, 5, 5, qrcode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fill version blocks
|
||||
if (version >= 7) {
|
||||
fillRectangle(qrsize - 11, 0, 3, 6, qrcode);
|
||||
@ -366,7 +366,7 @@ static void drawWhiteFunctionModules(uint8_t qrcode[], int version) {
|
||||
setModule(qrcode, 6, i, false);
|
||||
setModule(qrcode, i, 6, false);
|
||||
}
|
||||
|
||||
|
||||
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
|
||||
for (int i = -4; i <= 4; i++) {
|
||||
for (int j = -4; j <= 4; j++) {
|
||||
@ -380,7 +380,7 @@ static void drawWhiteFunctionModules(uint8_t qrcode[], int version) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw numerous alignment patterns
|
||||
uint8_t alignPatPos[7] = {0};
|
||||
int numAlign = getAlignmentPatternPositions(version, alignPatPos);
|
||||
@ -396,7 +396,7 @@ static void drawWhiteFunctionModules(uint8_t qrcode[], int version) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw version blocks
|
||||
if (version >= 7) {
|
||||
// Calculate error correction code and pack bits
|
||||
@ -405,7 +405,7 @@ static void drawWhiteFunctionModules(uint8_t qrcode[], int version) {
|
||||
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
|
||||
long data = (long)version << 12 | rem; // uint18
|
||||
assert(data >> 18 == 0);
|
||||
|
||||
|
||||
// Draw two copies
|
||||
for (int i = 0; i < 6; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
@ -440,7 +440,7 @@ static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uin
|
||||
data = data << 10 | rem;
|
||||
data ^= 0x5412; // uint15
|
||||
assert(data >> 15 == 0);
|
||||
|
||||
|
||||
// Draw first copy
|
||||
for (int i = 0; i <= 5; i++)
|
||||
setModule(qrcode, 8, i, ((data >> i) & 1) != 0);
|
||||
@ -449,7 +449,7 @@ static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uin
|
||||
setModule(qrcode, 7, 8, ((data >> 8) & 1) != 0);
|
||||
for (int i = 9; i < 15; i++)
|
||||
setModule(qrcode, 14 - i, 8, ((data >> i) & 1) != 0);
|
||||
|
||||
|
||||
// Draw second copy
|
||||
int qrsize = qrcodegen_getSize(qrcode);
|
||||
for (int i = 0; i <= 7; i++)
|
||||
@ -554,7 +554,7 @@ static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qr
|
||||
static long getPenaltyScore(const uint8_t qrcode[]) {
|
||||
int qrsize = qrcodegen_getSize(qrcode);
|
||||
long result = 0;
|
||||
|
||||
|
||||
// Adjacent modules in row having same color
|
||||
for (int y = 0; y < qrsize; y++) {
|
||||
bool colorX;
|
||||
@ -587,7 +587,7 @@ static long getPenaltyScore(const uint8_t qrcode[]) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 2*2 blocks of modules having same color
|
||||
for (int y = 0; y < qrsize - 1; y++) {
|
||||
for (int x = 0; x < qrsize - 1; x++) {
|
||||
@ -598,7 +598,7 @@ static long getPenaltyScore(const uint8_t qrcode[]) {
|
||||
result += PENALTY_N2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Finder-like pattern in rows
|
||||
for (int y = 0; y < qrsize; y++) {
|
||||
for (int x = 0, bits = 0; x < qrsize; x++) {
|
||||
@ -615,7 +615,7 @@ static long getPenaltyScore(const uint8_t qrcode[]) {
|
||||
result += PENALTY_N3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Balance of black and white modules
|
||||
int black = 0;
|
||||
for (int y = 0; y < qrsize; y++) {
|
||||
@ -734,7 +734,7 @@ testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars) {
|
||||
if (numChars > (unsigned int)LIMIT)
|
||||
return -1;
|
||||
int n = (int)numChars;
|
||||
|
||||
|
||||
int result = -2;
|
||||
if (mode == qrcodegen_Mode_NUMERIC) {
|
||||
// n * 3 + ceil(n / 3)
|
||||
@ -798,7 +798,7 @@ struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]
|
||||
if (bitLen > 0)
|
||||
memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0]));
|
||||
result.bitLength = 0;
|
||||
|
||||
|
||||
unsigned int accumData = 0;
|
||||
int accumCount = 0;
|
||||
for (; *digits != '\0'; digits++) {
|
||||
@ -832,7 +832,7 @@ struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t bu
|
||||
if (bitLen > 0)
|
||||
memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0]));
|
||||
result.bitLength = 0;
|
||||
|
||||
|
||||
unsigned int accumData = 0;
|
||||
int accumCount = 0;
|
||||
for (; *text != '\0'; text++) {
|
||||
@ -893,7 +893,7 @@ bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], siz
|
||||
assert(segs != NULL || len == 0);
|
||||
assert(qrcodegen_VERSION_MIN <= minVersion && minVersion <= maxVersion && maxVersion <= qrcodegen_VERSION_MAX);
|
||||
// assert(0 <= (int)ecl && (int)ecl <= 3 && -1 <= (int)mask && (int)mask <= 7);
|
||||
|
||||
|
||||
// Find the minimal version number to use
|
||||
int version, dataUsedBits;
|
||||
for (version = minVersion; ; version++) {
|
||||
@ -907,13 +907,13 @@ bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], siz
|
||||
}
|
||||
}
|
||||
assert(dataUsedBits != -1);
|
||||
|
||||
|
||||
// Increase the error correction level while the data still fits in the current version number
|
||||
for (int i = (int)qrcodegen_Ecc_MEDIUM; i <= (int)qrcodegen_Ecc_HIGH; i++) {
|
||||
if (boostEcl && dataUsedBits <= getNumDataCodewords(version, (enum qrcodegen_Ecc)i) * 8)
|
||||
ecl = (enum qrcodegen_Ecc)i;
|
||||
}
|
||||
|
||||
|
||||
// Create the data bit string by concatenating all segments
|
||||
int dataCapacityBits = getNumDataCodewords(version, ecl) * 8;
|
||||
memset(qrcode, 0, qrcodegen_BUFFER_LEN_FOR_VERSION(version) * sizeof(qrcode[0]));
|
||||
@ -934,26 +934,26 @@ bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], siz
|
||||
for (int j = 0; j < seg->bitLength; j++)
|
||||
appendBitsToBuffer((seg->data[j >> 3] >> (7 - (j & 7))) & 1, 1, qrcode, &bitLen);
|
||||
}
|
||||
|
||||
|
||||
// Add terminator and pad up to a byte if applicable
|
||||
int terminatorBits = dataCapacityBits - bitLen;
|
||||
if (terminatorBits > 4)
|
||||
terminatorBits = 4;
|
||||
appendBitsToBuffer(0, terminatorBits, qrcode, &bitLen);
|
||||
appendBitsToBuffer(0, (8 - bitLen % 8) % 8, qrcode, &bitLen);
|
||||
|
||||
|
||||
// Pad with alternate bytes until data capacity is reached
|
||||
for (uint8_t padByte = 0xEC; bitLen < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
|
||||
appendBitsToBuffer(padByte, 8, qrcode, &bitLen);
|
||||
assert(bitLen % 8 == 0);
|
||||
|
||||
|
||||
// Draw function and data codeword modules
|
||||
appendErrorCorrection(qrcode, version, ecl, tempBuffer);
|
||||
initializeFunctionModules(version, qrcode);
|
||||
drawCodewords(tempBuffer, getNumRawDataModules(version) / 8, qrcode);
|
||||
drawWhiteFunctionModules(qrcode, version);
|
||||
initializeFunctionModules(version, tempBuffer);
|
||||
|
||||
|
||||
// Handle masking
|
||||
if (mask == qrcodegen_Mask_AUTO) { // Automatically choose best mask
|
||||
long minPenalty = LONG_MAX;
|
||||
@ -1012,7 +1012,7 @@ static int numCharCountBits(enum qrcodegen_Mode mode, int version) {
|
||||
else if (10 <= version && version <= 26) i = 1;
|
||||
else if (27 <= version && version <= 40) i = 2;
|
||||
else assert(false);
|
||||
|
||||
|
||||
switch (mode) {
|
||||
case qrcodegen_Mode_NUMERIC : { const int temp[] = {10, 12, 14}; return temp[i]; }
|
||||
case qrcodegen_Mode_ALPHANUMERIC: { const int temp[] = { 9, 11, 13}; return temp[i]; }
|
||||
|
@ -1,9 +1,9 @@
|
||||
/*
|
||||
/*
|
||||
* QR Code generator library (C)
|
||||
*
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
@ -30,7 +30,7 @@
|
||||
|
||||
/*---- Enum and struct types----*/
|
||||
|
||||
/*
|
||||
/*
|
||||
* The error correction level used in a QR Code symbol.
|
||||
*/
|
||||
enum qrcodegen_Ecc {
|
||||
@ -41,7 +41,7 @@ enum qrcodegen_Ecc {
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* The mask pattern used in a QR Code symbol.
|
||||
*/
|
||||
enum qrcodegen_Mask {
|
||||
@ -60,7 +60,7 @@ enum qrcodegen_Mask {
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* The mode field of a segment.
|
||||
*/
|
||||
enum qrcodegen_Mode {
|
||||
@ -72,7 +72,7 @@ enum qrcodegen_Mode {
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* A segment of user/application data that a QR Code symbol can convey.
|
||||
* Each segment has a mode, a character count, and character/general data that is
|
||||
* already encoded as a sequence of bits. The maximum allowed bit length is 32767,
|
||||
@ -81,17 +81,17 @@ enum qrcodegen_Mode {
|
||||
struct qrcodegen_Segment {
|
||||
// The mode indicator for this segment.
|
||||
enum qrcodegen_Mode mode;
|
||||
|
||||
|
||||
// The length of this segment's unencoded data. Always in the range [0, 32767].
|
||||
// For numeric, alphanumeric, and kanji modes, this measures in Unicode code points.
|
||||
// For byte mode, this measures in bytes (raw binary data, text in UTF-8, or other encodings).
|
||||
// For ECI mode, this is always zero.
|
||||
int numChars;
|
||||
|
||||
|
||||
// The data bits of this segment, packed in bitwise big endian.
|
||||
// Can be null if the bit length is zero.
|
||||
uint8_t *data;
|
||||
|
||||
|
||||
// The number of valid data bits used in the buffer. Requires
|
||||
// 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8.
|
||||
int bitLength;
|
||||
@ -120,7 +120,7 @@ struct qrcodegen_Segment {
|
||||
|
||||
/*---- Functions to generate QR Codes ----*/
|
||||
|
||||
/*
|
||||
/*
|
||||
* Encodes the given text string to a QR Code symbol, returning true if encoding succeeded.
|
||||
* If the data is too long to fit in any version in the given range
|
||||
* at the given ECC level, then false is returned.
|
||||
@ -143,7 +143,7 @@ bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode
|
||||
enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl);
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* Encodes the given binary data to a QR Code symbol, returning true if encoding succeeded.
|
||||
* If the data is too long to fit in any version in the given range
|
||||
* at the given ECC level, then false is returned.
|
||||
@ -165,19 +165,19 @@ bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcod
|
||||
enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl);
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* Tests whether the given string can be encoded as a segment in alphanumeric mode.
|
||||
*/
|
||||
bool qrcodegen_isAlphanumeric(const char *text);
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* Tests whether the given string can be encoded as a segment in numeric mode.
|
||||
*/
|
||||
bool qrcodegen_isNumeric(const char *text);
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* Returns the number of bytes (uint8_t) needed for the data buffer of a segment
|
||||
* containing the given number of characters using the given mode. Notes:
|
||||
* - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or
|
||||
@ -191,19 +191,19 @@ bool qrcodegen_isNumeric(const char *text);
|
||||
size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars);
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* Returns a segment representing the given binary data encoded in byte mode.
|
||||
*/
|
||||
struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]);
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* Returns a segment representing the given string of decimal digits encoded in numeric mode.
|
||||
*/
|
||||
struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]);
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* Returns a segment representing the given text string encoded in alphanumeric mode.
|
||||
* The characters allowed are: 0 to 9, A to Z (uppercase only), space,
|
||||
* dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
@ -211,14 +211,14 @@ struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]
|
||||
struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]);
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* Returns a segment representing an Extended Channel Interpretation
|
||||
* (ECI) designator with the given assignment value.
|
||||
*/
|
||||
struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]);
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* Renders a QR Code symbol representing the given data segments at the given error correction
|
||||
* level or higher. The smallest possible QR Code version is automatically chosen for the output.
|
||||
* Returns true if QR Code creation succeeded, or false if the data is too long to fit in any version.
|
||||
@ -233,7 +233,7 @@ bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len,
|
||||
enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]);
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* Renders a QR Code symbol representing the given data segments with the given encoding parameters.
|
||||
* Returns true if QR Code creation succeeded, or false if the data is too long to fit in the range of versions.
|
||||
* The smallest possible QR Code version within the given range is automatically chosen for the output.
|
||||
@ -250,7 +250,7 @@ bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], siz
|
||||
|
||||
/*---- Functions to extract raw data from QR Codes ----*/
|
||||
|
||||
/*
|
||||
/*
|
||||
* Returns the side length of the given QR Code, assuming that encoding succeeded.
|
||||
* The result is in the range [21, 177]. Note that the length of the array buffer
|
||||
* is related to the side length - every 'uint8_t qrcode[]' must have length at least
|
||||
@ -259,7 +259,7 @@ bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], siz
|
||||
int qrcodegen_getSize(const uint8_t qrcode[]);
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* Returns the color of the module (pixel) at the given coordinates, which is either
|
||||
* false for white or true for black. The top left corner has the coordinates (x=0, y=0).
|
||||
* If the given coordinates are out of bounds, then false (white) is returned.
|
||||
|
@ -63,7 +63,7 @@
|
||||
#define __STACK_ABT_LEN 0x10000
|
||||
|
||||
#define __STACK_TOP (__STACK_ABT_TOP - __STACK_ABT_LEN)
|
||||
#define __STACK_LEN 0x7F0000
|
||||
#define __STACK_LEN 0x7F0000
|
||||
|
||||
#define __HEAP_ADDR (__FCRAM0_ADDR + 0x0001000)
|
||||
#define __HEAP_END (__STACK_TOP - __STACK_LEN)
|
||||
|
@ -20,7 +20,7 @@ bool spiflash_read(u32 offset, u32 size, u8 *buf)
|
||||
|
||||
ARM_WbDC_Range(ARM_GetSHMEM()->spiBuffer, blksz);
|
||||
PXI_DoCMD(PXI_NVRAM_READ, args, 2);
|
||||
ARM_InvDC_Range(ARM_GetSHMEM()->spiBuffer, blksz);
|
||||
ARM_InvDC_Range(ARM_GetSHMEM()->spiBuffer, blksz);
|
||||
ARM_DSB();
|
||||
memcpy(buf, ARM_GetSHMEM()->spiBuffer, blksz);
|
||||
|
||||
|
@ -3,38 +3,38 @@
|
||||
|
||||
u64 ReadAsciiOctal(char* num, u32 len) {
|
||||
u64 res = 0;
|
||||
|
||||
|
||||
if ((num[len-1] != '\0') && (num[len-1] != ' '))
|
||||
return (u64) -1;
|
||||
|
||||
|
||||
for (u32 i = 0; i < (len-1); i++) {
|
||||
res <<= 3;
|
||||
if ((num[i] >= '0') && (num[i] < '8')) res |= (num[i] - '0');
|
||||
else return (u64) -1;
|
||||
}
|
||||
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
u32 ValidateTarHeader(void* tardata, void* tardata_end) {
|
||||
TarHeader* tar = tardata;
|
||||
|
||||
|
||||
// available space
|
||||
if ((u8*) tardata_end < (u8*) tardata + sizeof(TarHeader))
|
||||
return 1;
|
||||
|
||||
|
||||
// filename prefix is not supported here
|
||||
if (*(tar->fname_prefix)) return 1;
|
||||
|
||||
|
||||
// check filesize
|
||||
u64 fsize_max = ((u8*) tardata_end - (u8*) tardata) - sizeof(TarHeader);
|
||||
if (ReadAsciiOctal(tar->fsize, 12) > fsize_max)
|
||||
return 1;
|
||||
|
||||
|
||||
// type can only be standard file or dir
|
||||
if (tar->ftype && (tar->ftype != '0') && (tar->ftype != '5'))
|
||||
return 1;
|
||||
|
||||
|
||||
// checksum
|
||||
u8* data = (u8*) tardata;
|
||||
u64 checksum = 0;
|
||||
@ -42,18 +42,18 @@ u32 ValidateTarHeader(void* tardata, void* tardata_end) {
|
||||
checksum += ((i < 148) || (i >= 156)) ? data[i] : (u64) ' ';
|
||||
if (checksum != ReadAsciiOctal(tar->checksum, 7))
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* GetTarFileInfo(void* tardata, char* fname, u64* fsize, bool* is_dir) {
|
||||
// this assumes a valid TAR header
|
||||
TarHeader* tar = tardata;
|
||||
|
||||
|
||||
if (fname) snprintf(fname, 101, "%.100s", tar->fname);
|
||||
if (fsize) *fsize = ReadAsciiOctal(tar->fsize, 12);
|
||||
if (is_dir) *is_dir = (tar->ftype == '5');
|
||||
|
||||
|
||||
return (void*) (tar + 1);
|
||||
}
|
||||
|
||||
@ -62,23 +62,23 @@ void* NextTarEntry(void* tardata, void* tardata_end) {
|
||||
TarHeader* tar = tardata;
|
||||
u8* data = (u8*) tardata;
|
||||
u64 fsize = ReadAsciiOctal(tar->fsize, 12);
|
||||
|
||||
|
||||
data += sizeof(TarHeader) + align(fsize, 512);
|
||||
if (ValidateTarHeader(data, tardata_end) != 0)
|
||||
return NULL;
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void* FindTarFileInfo(void* tardata, void* tardata_end, const char* fname, u64* fsize) {
|
||||
while (tardata && (tardata < tardata_end)) {
|
||||
TarHeader* tar = tardata;
|
||||
|
||||
|
||||
if (ValidateTarHeader(tardata, tardata_end) != 0) break;
|
||||
if ((strncasecmp(tar->fname, fname, 100) == 0) && (!tar->ftype || (tar->ftype == '0')))
|
||||
return GetTarFileInfo(tardata, NULL, fsize, NULL);
|
||||
tardata = ((u8*) tardata) + sizeof(TarHeader) + align(ReadAsciiOctal(tar->fsize, 12), 512);
|
||||
}
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
@ -31,15 +31,15 @@
|
||||
|
||||
#define FirstVTarEntry() \
|
||||
TARDATA
|
||||
|
||||
|
||||
#define OffsetVTarEntry(off) \
|
||||
TARDATA_(off)
|
||||
|
||||
#define NextVTarEntry(tardata) \
|
||||
NextTarEntry(tardata, TARDATA_END)
|
||||
|
||||
|
||||
#define GetVTarFileInfo(tardata, fname, fsize, is_dir) \
|
||||
GetTarFileInfo(tardata, fname, fsize, is_dir)
|
||||
|
||||
|
||||
#define FindVTarFileInfo(fname, fsize) \
|
||||
FindTarFileInfo(TARDATA, TARDATA_END, fname, fsize)
|
||||
|
@ -42,7 +42,7 @@ u32 CheckTransferableMbr(void* data) { // strict checks, custom partitions not a
|
||||
|
||||
u32 TransferCtrNandImage(const char* path_img, const char* drv) {
|
||||
if (!CheckWritePermissions(drv)) return 1;
|
||||
|
||||
|
||||
// backup current mount path, mount new path
|
||||
char path_store[256] = { 0 };
|
||||
char* path_bak = NULL;
|
||||
@ -53,7 +53,7 @@ u32 TransferCtrNandImage(const char* path_img, const char* drv) {
|
||||
InitImgFS(path_bak);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// CTRNAND preparations
|
||||
SecureInfo secnfo_img;
|
||||
SecureInfo secnfo_loc;
|
||||
@ -62,19 +62,19 @@ u32 TransferCtrNandImage(const char* path_img, const char* drv) {
|
||||
char path_secnfo_c[32];
|
||||
char path_tickdb[32];
|
||||
char path_tickdb_bak[32];
|
||||
|
||||
|
||||
snprintf(path_secnfo_a, 32, "%s/rw/sys/SecureInfo_A", drv);
|
||||
snprintf(path_secnfo_b, 32, "%s/rw/sys/SecureInfo_B", drv);
|
||||
snprintf(path_secnfo_c, 32, "%s/rw/sys/SecureInfo_C", drv);
|
||||
snprintf(path_tickdb, 32, "%s/dbs/ticket.db", drv);
|
||||
snprintf(path_tickdb_bak, 32, "%s/dbs/ticket.bak", drv);
|
||||
|
||||
|
||||
// special handling for out of region images (create SecureInfo_C)
|
||||
PathDelete(path_secnfo_c); // not required when transfering back to original region
|
||||
if (((FileGetData("7:/rw/sys/SecureInfo_A", (u8*) &secnfo_img, sizeof(SecureInfo), 0) == sizeof(SecureInfo)) ||
|
||||
if (((FileGetData("7:/rw/sys/SecureInfo_A", (u8*) &secnfo_img, sizeof(SecureInfo), 0) == sizeof(SecureInfo)) ||
|
||||
(FileGetData("7:/rw/sys/SecureInfo_B", (u8*) &secnfo_img, sizeof(SecureInfo), 0) == sizeof(SecureInfo))) &&
|
||||
((FileGetData(path_secnfo_a, (u8*) &secnfo_loc, sizeof(SecureInfo), 0) == sizeof(SecureInfo)) ||
|
||||
(FileGetData(path_secnfo_b, (u8*) &secnfo_loc, sizeof(SecureInfo), 0) == sizeof(SecureInfo))) &&
|
||||
((FileGetData(path_secnfo_a, (u8*) &secnfo_loc, sizeof(SecureInfo), 0) == sizeof(SecureInfo)) ||
|
||||
(FileGetData(path_secnfo_b, (u8*) &secnfo_loc, sizeof(SecureInfo), 0) == sizeof(SecureInfo))) &&
|
||||
(secnfo_img.region != secnfo_loc.region)) {
|
||||
secnfo_loc.region = secnfo_img.region;
|
||||
FileSetData(path_secnfo_c, (u8*) &secnfo_loc, sizeof(SecureInfo), 0, true);
|
||||
@ -95,7 +95,7 @@ u32 TransferCtrNandImage(const char* path_img, const char* drv) {
|
||||
drv, sha256sum[0], sha256sum[1], sha256sum[2], sha256sum[3]);
|
||||
PathDelete(path_asr);
|
||||
}
|
||||
|
||||
|
||||
// actual transfer - db files / titles
|
||||
static const char* dbnames[] = { "ticket.db", "certs.db", "title.db", "import.db", "tmp_t.db", "tmp_i.db" };
|
||||
char path_to[32];
|
||||
@ -115,7 +115,7 @@ u32 TransferCtrNandImage(const char* path_img, const char* drv) {
|
||||
snprintf(path_from, 32, "7:/title");
|
||||
PathDelete(path_to);
|
||||
PathCopy(drv, path_from, &flags);
|
||||
|
||||
|
||||
InitImgFS(path_bak);
|
||||
return 0;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,45 +8,45 @@
|
||||
u32 CryptAesKeyDb(const char* path, bool inplace, bool encrypt) {
|
||||
AesKeyInfo* keydb = NULL;
|
||||
const char* path_out = (inplace) ? path : OUTPUT_PATH "/" KEYDB_NAME;
|
||||
|
||||
|
||||
// write permissions
|
||||
if (!CheckWritePermissions(path_out))
|
||||
return 1;
|
||||
|
||||
|
||||
if (!inplace) {
|
||||
// ensure the output dir exists
|
||||
if (fvx_rmkdir(OUTPUT_PATH) != FR_OK)
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// check key database size
|
||||
u32 fsize = fvx_qsize(path);
|
||||
if (!fsize || (fsize % sizeof(AesKeyInfo)) || (fsize > MAX_KEYDB_SIZE))
|
||||
return 1;
|
||||
|
||||
|
||||
keydb = (AesKeyInfo*) malloc(fsize);
|
||||
if (!keydb) return 1;
|
||||
|
||||
|
||||
// load key database
|
||||
if (fvx_qread(path, keydb, 0, fsize, NULL) != FR_OK) {
|
||||
free(keydb);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// en-/decrypt keys
|
||||
u32 n_keys = fsize / sizeof(AesKeyInfo);
|
||||
for (u32 i = 0; i < n_keys; i++) {
|
||||
if ((bool) keydb[i].isEncrypted == !encrypt)
|
||||
CryptAesKeyInfo(&(keydb[i]));
|
||||
}
|
||||
|
||||
|
||||
// dump key database
|
||||
if (!inplace) f_unlink(path_out);
|
||||
if (fvx_qwrite(path_out, keydb, 0, fsize, NULL) != FR_OK) {
|
||||
free(keydb);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
free(keydb);
|
||||
return 0;
|
||||
}
|
||||
@ -72,35 +72,35 @@ u32 BuildKeyDb(const char* path, bool dump) {
|
||||
static AesKeyInfo* key_info = NULL;
|
||||
const char* path_out = OUTPUT_PATH "/" KEYDB_NAME;
|
||||
const char* path_in = path;
|
||||
|
||||
|
||||
// write permissions
|
||||
if (!CheckWritePermissions(path_out))
|
||||
return 1;
|
||||
|
||||
|
||||
if (!path_in && !dump) { // no input path given - initialize
|
||||
if (!key_info) key_info = (AesKeyInfo*) malloc(STD_BUFFER_SIZE);
|
||||
if (!key_info) return 1;
|
||||
memset(key_info, 0xFF, sizeof(AesKeyInfo));
|
||||
|
||||
|
||||
AddKeyToDb(key_info, NULL);
|
||||
if ((fvx_stat(path_out, NULL) == FR_OK) &&
|
||||
(ShowPrompt(true, "%s\nOutput file already exists.\nUpdate this?", path_out)))
|
||||
path_in = path_out;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
|
||||
// key info has to be allocated at this point
|
||||
if (!key_info) return 1;
|
||||
|
||||
|
||||
u64 filetype = path_in ? IdentifyFileType(path_in) : 0;
|
||||
if (filetype & BIN_KEYDB) { // AES key database
|
||||
u32 fsize = fvx_qsize(path_in);
|
||||
if ((fsize % sizeof(AesKeyInfo)) || (fsize > MAX_KEYDB_SIZE))
|
||||
return 1;
|
||||
|
||||
|
||||
u32 n_keys = fsize / sizeof(AesKeyInfo);
|
||||
u32 merged_keys = 0;
|
||||
|
||||
|
||||
AesKeyInfo* key_info_merge = (AesKeyInfo*) malloc(fsize);
|
||||
if (fvx_qread(path_in, key_info_merge, 0, fsize, NULL) == FR_OK) {
|
||||
for (u32 i = 0; i < n_keys; i++) {
|
||||
@ -110,7 +110,7 @@ u32 BuildKeyDb(const char* path, bool dump) {
|
||||
merged_keys++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
free(key_info_merge);
|
||||
if (merged_keys < n_keys) return 1;
|
||||
} else if (filetype & BIN_LEGKEY) { // legacy key file
|
||||
@ -118,17 +118,17 @@ u32 BuildKeyDb(const char* path, bool dump) {
|
||||
unsigned int keyslot = 0xFF;
|
||||
char typestr[32] = { 0 };
|
||||
char* name_in = strrchr(path_in, '/');
|
||||
|
||||
|
||||
memset(&key, 0, sizeof(AesKeyInfo));
|
||||
key.type = 'N';
|
||||
if (!name_in || (strnlen(++name_in, 32) > 24)) return 1; // safety
|
||||
if ((sscanf(name_in, "slot0x%02XKey%31s", &keyslot, typestr) != 2) &&
|
||||
(sscanf(name_in, "slot0x%02Xkey%31s", &keyslot, typestr) != 2)) return 1;
|
||||
|
||||
|
||||
char* ext = strchr(typestr, '.');
|
||||
if (!ext) return 1;
|
||||
*(ext++) = '\0';
|
||||
|
||||
|
||||
if ((*typestr == 'X') || (*typestr == 'Y')) {
|
||||
key.type = *typestr;
|
||||
strncpy(key.id, typestr + 1, 10);
|
||||
@ -143,7 +143,7 @@ u32 BuildKeyDb(const char* path, bool dump) {
|
||||
if (fvx_qread(path_in, key.key, 0, 16, NULL) != FR_OK) return 1;
|
||||
if (AddKeyToDb(key_info, &key) != 0) return 1;
|
||||
}
|
||||
|
||||
|
||||
if (dump) {
|
||||
u32 dump_size = 0;
|
||||
for (AesKeyInfo* key = key_info; key->slot <= 0x40; key++) {
|
||||
@ -151,7 +151,7 @@ u32 BuildKeyDb(const char* path, bool dump) {
|
||||
if (dump_size_next > MAX_KEYDB_SIZE) break;
|
||||
dump_size = dump_size_next;
|
||||
}
|
||||
|
||||
|
||||
if (dump_size) {
|
||||
if (fvx_rmkdir(OUTPUT_PATH) != FR_OK) // ensure the output dir exists
|
||||
return 1;
|
||||
@ -159,10 +159,10 @@ u32 BuildKeyDb(const char* path, bool dump) {
|
||||
if (fvx_qwrite(path_out, key_info, 0, dump_size, NULL) != FR_OK)
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
free(key_info);
|
||||
key_info = NULL;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -55,15 +55,15 @@
|
||||
u32 SetupSlot0x30(char drv) {
|
||||
u8 keyy[16] __attribute__((aligned(32)));
|
||||
char movable_path[32];
|
||||
|
||||
|
||||
if ((drv == 'A') || (drv == 'S')) drv = '1';
|
||||
else if ((drv == 'B') || (drv == 'E')) drv = '4';
|
||||
|
||||
|
||||
snprintf(movable_path, 32, "%c:/private/movable.sed", drv);
|
||||
if (fvx_qread(movable_path, keyy, 0x110, 0x10, NULL) != FR_OK) return 1;
|
||||
setup_aeskeyY(0x30, keyy);
|
||||
use_aeskey(0x30);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -117,12 +117,12 @@ u32 LocateAgbSaveSdCurrentSlot(const char* path, AgbSaveHeader* agbsave) {
|
||||
u32 CheckCmacHeader(const char* path) {
|
||||
u8 cmac_hdr[0x100];
|
||||
UINT br;
|
||||
|
||||
|
||||
if ((fvx_qread(path, cmac_hdr, 0, 0x100, &br) != FR_OK) || (br != 0x100))
|
||||
return 1;
|
||||
for (u32 i = 0x10; i < 0x100; i++)
|
||||
if (cmac_hdr[i] != 0x00) return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -133,14 +133,14 @@ u32 CheckCmacPath(const char* path) {
|
||||
u32 ReadWriteFileCmac(const char* path, u8* cmac, bool do_write, bool check_perms) {
|
||||
u32 cmac_type = CalculateFileCmac(path, NULL);
|
||||
u32 offset = 0;
|
||||
|
||||
|
||||
if (!cmac_type) return 1;
|
||||
else if (cmac_type == CMAC_MOVABLE) offset = 0x130;
|
||||
else if (cmac_type == CMAC_AGBSAVE) offset = 0x010;
|
||||
else if (cmac_type == CMAC_AGBSAVE_SD) offset = LocateAgbSaveSdCurrentSlot(path, NULL) + 0x10;
|
||||
else if ((cmac_type == CMAC_CMD_SD) || (cmac_type == CMAC_CMD_TWLN)) return 1; // can't do that here
|
||||
else offset = 0x000;
|
||||
|
||||
|
||||
if (do_write && check_perms && !CheckWritePermissions(path)) return 1;
|
||||
if (!do_write) return (fvx_qread(path, cmac, offset, 0x10, NULL) != FR_OK) ? 1 : 0;
|
||||
else return (fvx_qwrite(path, cmac, offset, 0x10, NULL) != FR_OK) ? 1 : 0;
|
||||
@ -155,13 +155,13 @@ u32 CalculateFileCmac(const char* path, u8* cmac) {
|
||||
u32 sid; // save ID / various uses
|
||||
char* name;
|
||||
char* ext;
|
||||
|
||||
|
||||
name = strrchr(path, '/'); // filename
|
||||
if (!name) return 0; // will not happen
|
||||
name++;
|
||||
ext = strrchr(name, '.'); // extension
|
||||
if (ext) ext++;
|
||||
|
||||
|
||||
if ((drv == 'A') || (drv == 'B')) { // data installed on SD
|
||||
if (sscanf(path, "%c:/extdata/%08lx/%08lx/%08lx/%08lx", &drv, &xid_high, &xid_low, &fid_high, &fid_low) == 5) {
|
||||
sid = 1;
|
||||
@ -191,7 +191,7 @@ u32 CalculateFileCmac(const char* path, u8* cmac) {
|
||||
cmac_type = CMAC_CMD_TWLN; // this is not supported (yet), it's in here just for detection
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!cmac_type) { // path independent stuff
|
||||
const char* db_names[] = { SYS_DB_NAMES };
|
||||
for (sid = 0; sid < sizeof(db_names) / sizeof(char*); sid++)
|
||||
@ -203,28 +203,28 @@ u32 CalculateFileCmac(const char* path, u8* cmac) {
|
||||
else if (strncasecmp(name, "agbsave.bin", 16) == 0)
|
||||
cmac_type = CMAC_AGBSAVE;
|
||||
}
|
||||
|
||||
|
||||
// exit with cmac_type if (u8*) cmac is NULL
|
||||
// somewhat hacky, but can be used to check if file has a CMAC
|
||||
if (!cmac) return cmac_type;
|
||||
else if ((cmac_type == CMAC_CMD_SD) || (cmac_type == CMAC_CMD_TWLN)) return 1;
|
||||
else if (!cmac_type) return 1;
|
||||
|
||||
|
||||
static const u32 cmac_keyslot[] = { CMAC_KEYSLOT };
|
||||
u8 hashdata[0x200] __attribute__((aligned(4)));
|
||||
u8 hashdata[0x200] __attribute__((aligned(4)));
|
||||
u32 keyslot = cmac_keyslot[cmac_type];
|
||||
u32 hashsize = 0;
|
||||
|
||||
|
||||
// setup slot 0x30 via movable.sed
|
||||
if ((keyslot == 0x30) && (SetupSlot0x30(drv) != 0))
|
||||
return 1;
|
||||
|
||||
|
||||
// build hash data block, get size
|
||||
if ((cmac_type == CMAC_AGBSAVE) || (cmac_type == CMAC_AGBSAVE_SD)) { // agbsaves
|
||||
AgbSaveHeader* agbsave = (AgbSaveHeader*) malloc(AGBSAVE_MAX_SIZE);
|
||||
u32 offset = 0;
|
||||
UINT br;
|
||||
|
||||
|
||||
if (!agbsave) return 1;
|
||||
if (cmac_type == CMAC_AGBSAVE_SD) offset = LocateAgbSaveSdCurrentSlot(path, NULL);
|
||||
if ((fvx_qread(path, agbsave, offset, AGBSAVE_MAX_SIZE, &br) != FR_OK) || (br < 0x200) ||
|
||||
@ -232,7 +232,7 @@ u32 CalculateFileCmac(const char* path, u8* cmac) {
|
||||
free(agbsave);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
u32 ret = FixAgbSaveCmac(agbsave, cmac, (cmac_type == CMAC_AGBSAVE) ? NULL : path);
|
||||
free(agbsave);
|
||||
return ret;
|
||||
@ -275,14 +275,14 @@ u32 CalculateFileCmac(const char* path, u8* cmac) {
|
||||
hashsize = 0x10C;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// calculate CMAC
|
||||
u8 shasum[32];
|
||||
if (!hashsize) return 1;
|
||||
sha_quick(shasum, hashdata, hashsize, SHA256_MODE);
|
||||
use_aeskey(keyslot);
|
||||
aes_cmac(shasum, cmac, 2);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -311,11 +311,11 @@ u32 FixFileCmac(const char* path, bool check_perms) {
|
||||
u32 FixAgbSaveCmac(void* data, u8* cmac, const char* sddrv) {
|
||||
AgbSaveHeader* agbsave = (AgbSaveHeader*) (void*) data;
|
||||
u8 temp[0x30] __attribute__((aligned(4))); // final hash @temp+0x00
|
||||
|
||||
|
||||
// safety check
|
||||
if (ValidateAgbSaveHeader(agbsave) != 0)
|
||||
return 1;
|
||||
|
||||
|
||||
if (!sddrv) { // NAND partition mode
|
||||
sha_quick(temp + 0x00, (u8*) data + 0x30, (0x200 - 0x30) + agbsave->save_size, SHA256_MODE);
|
||||
} else {
|
||||
@ -325,7 +325,7 @@ u32 FixAgbSaveCmac(void* data, u8* cmac, const char* sddrv) {
|
||||
// this won't work on devkits(!!!)
|
||||
const char* cmac_savetype[] = { CMAC_SAVETYPE };
|
||||
if (SetupSlot0x30(*sddrv) != 0) return 1;
|
||||
|
||||
|
||||
// first hash (hash0 = AGBSAVE_hash)
|
||||
sha_quick(temp + 0x08, (u8*) data + 0x30, (0x200 - 0x30) + agbsave->save_size, SHA256_MODE);
|
||||
// second hash (hash1 = CTR-SAV0 + hash0)
|
||||
@ -336,11 +336,11 @@ u32 FixAgbSaveCmac(void* data, u8* cmac, const char* sddrv) {
|
||||
memcpy(temp + 0x08, &(agbsave->title_id), 8);
|
||||
sha_quick(temp + 0x00, temp, 0x30, SHA256_MODE);
|
||||
}
|
||||
|
||||
|
||||
use_aeskey((sddrv) ? 0x30 : 0x24);
|
||||
aes_cmac(temp, &(agbsave->cmac), 2);
|
||||
if (cmac) memcpy(cmac, &(agbsave->cmac), 0x10);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -368,7 +368,7 @@ u32 CheckFixCmdCmac(const char* path, bool fix, bool check_perms) {
|
||||
u64 cmd_size = fvx_qsize(path);
|
||||
u8* cmd_data = malloc(cmd_size);
|
||||
CmdHeader* cmd = (CmdHeader*) (void*) cmd_data;
|
||||
|
||||
|
||||
// check for out of memory
|
||||
if (cmd_data == NULL) return 1;
|
||||
|
||||
@ -454,13 +454,13 @@ u32 RecursiveFixFileCmacWorker(char* path) {
|
||||
FILINFO fno;
|
||||
DIR pdir;
|
||||
u32 err = 0;
|
||||
|
||||
|
||||
if (fvx_opendir(&pdir, path) == FR_OK) { // process folder contents
|
||||
char pathstr[32 + 1];
|
||||
TruncateString(pathstr, path, 32, 8);
|
||||
char* fname = path + strnlen(path, 255);
|
||||
*(fname++) = '/';
|
||||
|
||||
|
||||
ShowString("%s\nFixing CMACs, please wait...", pathstr);
|
||||
while (f_readdir(&pdir, &fno) == FR_OK) {
|
||||
if ((strncmp(fno.fname, ".", 2) == 0) || (strncmp(fno.fname, "..", 3) == 0))
|
||||
@ -479,16 +479,16 @@ u32 RecursiveFixFileCmacWorker(char* path) {
|
||||
*(--fname) = '\0';
|
||||
} else if (CheckCmacPath(path) == 0) // fix single file CMAC
|
||||
return FixFileCmac(path, true);
|
||||
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
u32 RecursiveFixFileCmac(const char* path) {
|
||||
// create a fixed up local path
|
||||
// (this is highly path sensitive)
|
||||
char lpath[256];
|
||||
char lpath[256];
|
||||
char* p = (char*) path;
|
||||
lpath[255] = '\0';
|
||||
lpath[255] = '\0';
|
||||
for (u32 i = 0; i < 255; i++) {
|
||||
lpath[i] = *(p++);
|
||||
while ((lpath[i] == '/') && (*p == '/')) p++;
|
||||
|
@ -50,7 +50,7 @@ u32 BuildEssentialBackup(const char* path, EssentialBackup* essential) {
|
||||
};
|
||||
memset(essential, 0, sizeof(EssentialBackup));
|
||||
memcpy(essential, filelist, sizeof(filelist));
|
||||
|
||||
|
||||
// backup current mount path, mount new path
|
||||
char path_store[256] = { 0 };
|
||||
char* path_bak = NULL;
|
||||
@ -61,7 +61,7 @@ u32 BuildEssentialBackup(const char* path, EssentialBackup* essential) {
|
||||
InitImgFS(path_bak);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// read four files
|
||||
ExeFsFileHeader* files = essential->header.files;
|
||||
if ((fvx_qread("I:/nand_hdr.bin", &(essential->nand_hdr), 0, 0x200, (UINT*) &(files[0].size)) != FR_OK) ||
|
||||
@ -73,34 +73,34 @@ u32 BuildEssentialBackup(const char* path, EssentialBackup* essential) {
|
||||
InitImgFS(path_bak);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// HWCAL0.dat / HWCAL1.dat
|
||||
if ((fvx_qread("7:/ro/sys/HWCAL0.dat", &(essential->hwcal0), 0, 0x1000, (UINT*) &(files[6].size)) != FR_OK) ||
|
||||
(fvx_qread("7:/ro/sys/HWCAL1.dat", &(essential->hwcal1), 0, 0x1000, (UINT*) &(files[7].size)) != FR_OK)) {
|
||||
memset(&(filelist[6]), 0, 2 * sizeof(ExeFsFileHeader));
|
||||
}
|
||||
|
||||
|
||||
// mount original file
|
||||
InitImgFS(path_bak);
|
||||
|
||||
|
||||
// fill nand cid / otp hash
|
||||
sdmmc_get_cid(1, (u32*) (void*) &(essential->nand_cid));
|
||||
if (!IS_UNLOCKED) memset(&(filelist[5]), 0, 3 * sizeof(ExeFsFileHeader));
|
||||
else memcpy(&(essential->otp), (u8*) __OTP_ADDR, 0x100);
|
||||
|
||||
|
||||
// calculate hashes
|
||||
for (u32 i = 0; i < 8 && *(filelist[i].name); i++)
|
||||
for (u32 i = 0; i < 8 && *(filelist[i].name); i++)
|
||||
sha_quick(essential->header.hashes[9-i],
|
||||
((u8*) essential) + files[i].offset + sizeof(ExeFsHeader),
|
||||
files[i].size, SHA256_MODE);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 CheckEmbeddedBackup(const char* path) {
|
||||
EssentialBackup* essential = (EssentialBackup*) malloc(sizeof(EssentialBackup));
|
||||
EssentialBackup* embedded = (EssentialBackup*) malloc(sizeof(EssentialBackup));
|
||||
|
||||
|
||||
if (!essential || !embedded || (BuildEssentialBackup(path, essential) != 0) ||
|
||||
(fvx_qread(path, embedded, SECTOR_D0K3 * 0x200, sizeof(EssentialBackup), NULL) != FR_OK) ||
|
||||
(memcmp(embedded, essential, sizeof(EssentialBackup)) != 0)) {
|
||||
@ -108,7 +108,7 @@ u32 CheckEmbeddedBackup(const char* path) {
|
||||
free(embedded);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
free(essential);
|
||||
free(embedded);
|
||||
return 0;
|
||||
@ -117,7 +117,7 @@ u32 CheckEmbeddedBackup(const char* path) {
|
||||
u32 EmbedEssentialBackup(const char* path) {
|
||||
EssentialBackup* essential = (EssentialBackup*) malloc(sizeof(EssentialBackup));
|
||||
if (!essential) return 1;
|
||||
|
||||
|
||||
// leaving out the write permissions check here, it's okay
|
||||
if ((BuildEssentialBackup(path, essential) != 0) ||
|
||||
(ValidateNandNcsdHeader((void*)essential->nand_hdr) != 0) ||
|
||||
@ -125,7 +125,7 @@ u32 EmbedEssentialBackup(const char* path) {
|
||||
free(essential);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
free(essential);
|
||||
return 0;
|
||||
}
|
||||
@ -133,27 +133,27 @@ u32 EmbedEssentialBackup(const char* path) {
|
||||
u32 DumpGbaVcSavegameBuffered(const char* path, void* buffer) {
|
||||
AgbSaveHeader* agbsave = (AgbSaveHeader*) buffer;
|
||||
u8* savegame = (u8*) (agbsave + 1);
|
||||
|
||||
|
||||
// read full AGBsave to memory
|
||||
if ((fvx_qread(path, agbsave, 0, sizeof(AgbSaveHeader), NULL) != FR_OK) || (ValidateAgbSaveHeader(agbsave) != 0) ||
|
||||
(fvx_qread(path, savegame, sizeof(AgbSaveHeader), agbsave->save_size, NULL) != FR_OK)) return 1; // not a proper AGBSAVE file
|
||||
|
||||
|
||||
// byteswap for eeprom type saves (512 byte / 8 kB)
|
||||
if ((agbsave->save_size == GBASAVE_EEPROM_512) || (agbsave->save_size == GBASAVE_EEPROM_8K)) {
|
||||
for (u8* ptr = savegame; (ptr - savegame) < (int) agbsave->save_size; ptr += 8)
|
||||
*(u64*) (void*) ptr = getbe64(ptr);
|
||||
}
|
||||
|
||||
|
||||
// ensure the output dir exists
|
||||
if (fvx_rmkdir(OUTPUT_PATH) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
// generate output path
|
||||
char path_vcsav[64];
|
||||
snprintf(path_vcsav, 64, OUTPUT_PATH "/%016llX.gbavc.sav", agbsave->title_id);
|
||||
if (fvx_qwrite(path_vcsav, savegame, 0, agbsave->save_size, NULL) != FR_OK) return 1; // write fail
|
||||
|
||||
return 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 DumpGbaVcSavegame(const char* path) {
|
||||
@ -162,7 +162,7 @@ u32 DumpGbaVcSavegame(const char* path) {
|
||||
ShowPrompt(false, "Out of memory.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
u32 ret = DumpGbaVcSavegameBuffered(path, buffer);
|
||||
free(buffer);
|
||||
return ret;
|
||||
@ -171,7 +171,7 @@ u32 DumpGbaVcSavegame(const char* path) {
|
||||
u32 InjectGbaVcSavegameBuffered(const char* path, const char* path_vcsave, void* buffer) {
|
||||
AgbSaveHeader* agbsave = (AgbSaveHeader*) buffer;
|
||||
u8* savegame = (u8*) (agbsave + 1);
|
||||
|
||||
|
||||
// basic sanity checks for path_vcsave
|
||||
FILINFO fno;
|
||||
char* ext = strrchr(path_vcsave, '.');
|
||||
@ -179,27 +179,27 @@ u32 InjectGbaVcSavegameBuffered(const char* path, const char* path_vcsave, void*
|
||||
(strncasecmp(ext+1, "SaveRAM", 8) != 0))) return 1; // bad extension
|
||||
if ((fvx_stat(path_vcsave, &fno) != FR_OK) || !GBASAVE_VALID(fno.fsize))
|
||||
return 1; // bad size
|
||||
|
||||
|
||||
// read AGBsave header to memory
|
||||
if ((fvx_qread(path, agbsave, 0, sizeof(AgbSaveHeader), NULL) != FR_OK) ||
|
||||
(ValidateAgbSaveHeader(agbsave) != 0)) return 1; // not a proper header
|
||||
|
||||
|
||||
// read savegame to memory
|
||||
u32 inject_save_size = min(agbsave->save_size, fno.fsize);
|
||||
memset(savegame, 0xFF, agbsave->save_size); // pad with 0xFF
|
||||
if (fvx_qread(path_vcsave, savegame, 0, inject_save_size, NULL) != FR_OK) return 1;
|
||||
|
||||
|
||||
// byteswap for eeprom type saves (512 byte / 8 kB)
|
||||
if ((agbsave->save_size == GBASAVE_EEPROM_512) || (agbsave->save_size == GBASAVE_EEPROM_8K)) {
|
||||
for (u8* ptr = savegame; (ptr - savegame) < (int) inject_save_size; ptr += 8)
|
||||
*(u64*) (void*) ptr = getbe64(ptr);
|
||||
}
|
||||
|
||||
|
||||
// fix CMAC for NAND partition, rewrite AGBSAVE file
|
||||
u32 data_size = sizeof(AgbSaveHeader) + agbsave->save_size;
|
||||
if (FixAgbSaveCmac(agbsave, NULL, NULL) != 0) return 1;
|
||||
if (fvx_qwrite(path, agbsave, 0, data_size, NULL) != FR_OK) return 1; // write fail
|
||||
|
||||
|
||||
// fix CMAC for SD partition, take it over to SD
|
||||
if (strncasecmp(path, "S:/agbsave.bin", 256) == 0) {
|
||||
AgbSaveHeader agbsave_sd;
|
||||
@ -217,18 +217,18 @@ u32 InjectGbaVcSavegameBuffered(const char* path, const char* path_vcsave, void*
|
||||
(ValidateAgbSaveHeader(&agbsave_sd) == 0) &&
|
||||
(agbsave->times_saved == agbsave_sd.times_saved))
|
||||
slot = data_size; // proper slot is bottom slot (otherwise it's the top slot)
|
||||
|
||||
|
||||
// inject next slot
|
||||
agbsave->times_saved++; // increase # of times saved
|
||||
if (FixAgbSaveCmac(agbsave, NULL, path_sd) != 0) return 1;
|
||||
if (fvx_qwrite(path_sd, agbsave, slot, data_size, NULL) != FR_OK) return 1; // write fail
|
||||
}
|
||||
|
||||
|
||||
// set CFG_BOOTENV to 0x7 so the save is taken over (not needed anymore)
|
||||
// https://www.3dbrew.org/wiki/CONFIG9_Registers#CFG9_BOOTENV
|
||||
// if (strncasecmp(path, "S:/agbsave.bin", 256) == 0) *(u32*) 0x10010000 = 0x7;
|
||||
|
||||
return 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 InjectGbaVcSavegame(const char* path, const char* path_vcsave) {
|
||||
@ -237,7 +237,7 @@ u32 InjectGbaVcSavegame(const char* path, const char* path_vcsave) {
|
||||
ShowPrompt(false, "Out of memory.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
u32 ret = InjectGbaVcSavegameBuffered(path, path_vcsave, buffer);
|
||||
free(buffer);
|
||||
return ret;
|
||||
@ -246,54 +246,54 @@ u32 InjectGbaVcSavegame(const char* path, const char* path_vcsave) {
|
||||
u32 RebuildNandNcsdHeader(NandNcsdHeader* ncsd) {
|
||||
// signature (retail or dev)
|
||||
const u8* signature = (IS_DEVKIT) ? sig_nand_ncsd_dev : sig_nand_ncsd_retail;
|
||||
|
||||
|
||||
// encrypted TWL MBR
|
||||
u8 twl_mbr_data[0x200] = { 0 };
|
||||
u8* twl_mbr = twl_mbr_data + (0x200 - sizeof(twl_mbr_std));
|
||||
memcpy(twl_mbr, twl_mbr_std, sizeof(twl_mbr_std));
|
||||
CryptNand(twl_mbr_data, 0, 1, 0x03);
|
||||
|
||||
|
||||
// rebuild NAND header for console
|
||||
memset(ncsd, 0x00, sizeof(NandNcsdHeader));
|
||||
memset(ncsd, 0x00, sizeof(NandNcsdHeader));
|
||||
memcpy(ncsd->signature, signature, 0x100); // signature
|
||||
memcpy(ncsd->twl_mbr, twl_mbr, 0x42); // TWL MBR
|
||||
memcpy(ncsd->magic, "NCSD", 0x4); // magic number
|
||||
ncsd->size = (IS_O3DS) ? 0x200000 : 0x280000; // total size
|
||||
|
||||
|
||||
// TWL partition (0)
|
||||
ncsd->partitions_fs_type[0] = 0x01;
|
||||
ncsd->partitions_crypto_type[0] = 0x01;
|
||||
ncsd->partitions[0].offset = 0x000000;
|
||||
ncsd->partitions[0].size = 0x058800;
|
||||
|
||||
|
||||
// AGBSAVE partition (1)
|
||||
ncsd->partitions_fs_type[1] = 0x04;
|
||||
ncsd->partitions_crypto_type[1] = 0x02;
|
||||
ncsd->partitions[1].offset = 0x058800;
|
||||
ncsd->partitions[1].size = 0x000180;
|
||||
|
||||
|
||||
// FIRM0 partition (2)
|
||||
ncsd->partitions_fs_type[2] = 0x03;
|
||||
ncsd->partitions_crypto_type[2] = 0x02;
|
||||
ncsd->partitions[2].offset = 0x058980;
|
||||
ncsd->partitions[2].size = 0x002000;
|
||||
|
||||
|
||||
// FIRM1 partition (3)
|
||||
ncsd->partitions_fs_type[3] = 0x03;
|
||||
ncsd->partitions_crypto_type[3] = 0x02;
|
||||
ncsd->partitions[3].offset = 0x05A980;
|
||||
ncsd->partitions[3].size = 0x002000;
|
||||
|
||||
|
||||
// CTR partition (4)
|
||||
ncsd->partitions_fs_type[4] = 0x01;
|
||||
ncsd->partitions_crypto_type[4] = (IS_O3DS) ? 0x02 : 0x03;
|
||||
ncsd->partitions[4].offset = 0x05C980;
|
||||
ncsd->partitions[4].size = (IS_O3DS) ? 0x17AE80 : 0x20F680;
|
||||
|
||||
|
||||
// unknown stuff - whatever this is ¯\_(ツ)_/¯
|
||||
ncsd->unknown[0x25] = 0x04;
|
||||
ncsd->unknown[0x2C] = 0x01;
|
||||
|
||||
|
||||
// done
|
||||
return 0;
|
||||
}
|
||||
@ -301,13 +301,13 @@ u32 RebuildNandNcsdHeader(NandNcsdHeader* ncsd) {
|
||||
u32 FixNandHeader(const char* path, bool check_size) {
|
||||
NandNcsdHeader ncsd;
|
||||
if (RebuildNandNcsdHeader(&ncsd) != 0) return 1;
|
||||
|
||||
|
||||
// safety check
|
||||
FILINFO fno;
|
||||
FSIZE_t min_size = check_size ? GetNandNcsdMinSizeSectors(&ncsd) * 0x200 : 0x200;
|
||||
if ((fvx_stat(path, &fno) != FR_OK) || (min_size > fno.fsize))
|
||||
return 1;
|
||||
|
||||
|
||||
// inject to path
|
||||
if (!CheckWritePermissions(path)) return 1;
|
||||
return (fvx_qwrite(path, &ncsd, 0x0, 0x200, NULL) == FR_OK) ? 0 : 1;
|
||||
@ -316,15 +316,15 @@ u32 FixNandHeader(const char* path, bool check_size) {
|
||||
u32 ValidateNandDump(const char* path) {
|
||||
NandPartitionInfo info;
|
||||
FIL file;
|
||||
|
||||
|
||||
// truncated path string
|
||||
char pathstr[32 + 1];
|
||||
TruncateString(pathstr, path, 32, 8);
|
||||
|
||||
|
||||
// open file
|
||||
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
|
||||
|
||||
// check NAND header
|
||||
NandNcsdHeader ncsd;
|
||||
if ((ReadNandFile(&file, &ncsd, 0, 1, 0xFF) != 0) || (ValidateNandNcsdHeader(&ncsd) != 0)) {
|
||||
@ -332,14 +332,14 @@ u32 ValidateNandDump(const char* path) {
|
||||
fvx_close(&file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// check size
|
||||
if (fvx_size(&file) < (GetNandNcsdMinSizeSectors(&ncsd) * 0x200)) {
|
||||
ShowPrompt(false, "%s\nNAND dump misses data", pathstr);
|
||||
fvx_close(&file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// check TWL & CTR FAT partitions
|
||||
for (u32 i = 0; i < 2; i++) {
|
||||
char* section_type = (i) ? "CTR" : "MBR";
|
||||
@ -366,11 +366,11 @@ u32 ValidateNandDump(const char* path) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check FIRMs (at least one FIRM must be valid)
|
||||
u8* firm = (u8*) malloc(FIRM_MAX_SIZE);
|
||||
if (!firm) return 1;
|
||||
|
||||
|
||||
// check all 8 firms, also check if ARM9 & ARM11 entrypoints are available
|
||||
for (u32 f = 0; f <= 8; f++) {
|
||||
if (GetNandNcsdPartitionInfo(&info, NP_TYPE_FIRM, NP_SUBTYPE_CTR, f, &ncsd) != 0) {
|
||||
@ -379,17 +379,17 @@ u32 ValidateNandDump(const char* path) {
|
||||
free(firm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
u32 firm_size = info.count * 0x200;
|
||||
if ((firm_size <= FIRM_MAX_SIZE) &&
|
||||
(ReadNandFile(&file, firm, info.sector, info.count, info.keyslot) == 0) &&
|
||||
(ValidateFirm(firm, firm_size, true) == 0))
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
free(firm);
|
||||
fvx_close(&file);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -406,17 +406,17 @@ u32 SafeRestoreNandDump(const char* path) {
|
||||
EmbedEssentialBackup("S:/nand.bin");
|
||||
else return 1;
|
||||
}
|
||||
|
||||
|
||||
if (!ShowUnlockSequence(5, "!WARNING!\n \nProceeding will overwrite the\nSysNAND with the provided dump.\n \n(B9S/A9LH will be left intact.)"))
|
||||
return 1;
|
||||
if (!SetWritePermissions(PERM_SYS_LVL1, true)) return 1;
|
||||
|
||||
|
||||
// open file, get size
|
||||
FIL file;
|
||||
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
u32 fsize = fvx_size(&file);
|
||||
|
||||
|
||||
// get NCSD headers from image and SysNAND
|
||||
NandNcsdHeader ncsd_loc, ncsd_img;
|
||||
MbrHeader twl_mbr_img;
|
||||
@ -426,7 +426,7 @@ u32 SafeRestoreNandDump(const char* path) {
|
||||
fvx_close(&file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// compare NCSD header partitioning
|
||||
// FIRMS must be at the same place for image and local NAND
|
||||
bool header_inject = false;
|
||||
@ -452,7 +452,7 @@ u32 SafeRestoreNandDump(const char* path) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// additional warning for elevated write permissions
|
||||
if (header_inject) {
|
||||
if (!ShowPrompt(true, "!WARNING!\n \nNCSD differs between image and local,\nelevated write permissions required\n \nProceed on your own risk?") ||
|
||||
@ -461,13 +461,13 @@ u32 SafeRestoreNandDump(const char* path) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
||||
if (!buffer) {
|
||||
fvx_close(&file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// main processing loop
|
||||
u32 ret = 0;
|
||||
u32 sector0 = SECTOR_SECRET + COUNT_SECRET; // start at the sector after secret sector
|
||||
@ -487,22 +487,22 @@ u32 SafeRestoreNandDump(const char* path) {
|
||||
if (sector1 == fsize / 0x200) break; // at file end
|
||||
sector0 = np_info.sector + np_info.count; // skip partition
|
||||
}
|
||||
|
||||
|
||||
free(buffer);
|
||||
fvx_close(&file);
|
||||
|
||||
|
||||
// NCSD header inject, should only be required with 2.1 local NANDs on N3DS
|
||||
if (header_inject && (ret == 0) &&
|
||||
(WriteNandSectors((u8*) &ncsd_img, 0, 1, 0xFF, NAND_SYSNAND) != 0))
|
||||
ret = 1;
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32 SafeInstallFirmBuffered(const char* path, u32 slots, u8* buffer, u32 bufsiz) {
|
||||
char pathstr[32 + 1]; // truncated path string
|
||||
TruncateString(pathstr, path, 32, 8);
|
||||
|
||||
|
||||
// load / check FIRM
|
||||
u8* firm = buffer;
|
||||
UINT firm_size;
|
||||
@ -512,12 +512,12 @@ u32 SafeInstallFirmBuffered(const char* path, u32 slots, u8* buffer, u32 bufsiz)
|
||||
"%s\nNot a installable FIRM." : "%s\nFIRM load/verify error.", pathstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// inject sighax signature, get hash
|
||||
u8 firm_sha[0x20];
|
||||
memcpy(firm + 0x100, (IS_DEVKIT) ? sig_nand_firm_dev : sig_nand_firm_retail, 0x100);
|
||||
sha_quick(firm_sha, firm, firm_size, SHA256_MODE);
|
||||
|
||||
|
||||
// check install slots
|
||||
for (u32 s = 0; s < 8; s++) {
|
||||
NandPartitionInfo info;
|
||||
@ -528,7 +528,7 @@ u32 SafeInstallFirmBuffered(const char* path, u32 slots, u8* buffer, u32 bufsiz)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check sector 0x96 on N3DS, offer fix if required
|
||||
u8 sector0x96[0x200] __attribute__((aligned(4)));
|
||||
bool fix_sector0x96 = false;
|
||||
@ -552,11 +552,11 @@ u32 SafeInstallFirmBuffered(const char* path, u32 slots, u8* buffer, u32 bufsiz)
|
||||
fix_sector0x96 = true;
|
||||
} else return 1;
|
||||
}
|
||||
|
||||
|
||||
// all checked, ready to go
|
||||
if (!ShowUnlockSequence(6, "!WARNING!\n \nProceeding will install the\nprovided FIRM to the SysNAND\nand inject sighax.\n \nInstalling an unsupported FIRM\nwill BRICK your console!")) return 1;
|
||||
// if (!SetWritePermissions(PERM_SYS_LVL3, true)) return 1; // one unlock sequence is enough
|
||||
|
||||
|
||||
// point of no return
|
||||
ShowString(false, "Installing FIRM, please wait...");
|
||||
if (fix_sector0x96 && (WriteNandSectors(sector0x96, 0x96, 1, 0x11, NAND_SYSNAND) != 0)) {
|
||||
@ -572,7 +572,7 @@ u32 SafeInstallFirmBuffered(const char* path, u32 slots, u8* buffer, u32 bufsiz)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// done, now check the installation
|
||||
ShowString(false, "Checking installation, please wait...");
|
||||
if (fix_sector0x96 && ((ReadNandSectors(sector0x96, 0x96, 1, 0x11, NAND_SYSNAND) != 0) ||
|
||||
@ -590,7 +590,7 @@ u32 SafeInstallFirmBuffered(const char* path, u32 slots, u8* buffer, u32 bufsiz)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -600,7 +600,7 @@ u32 SafeInstallFirm(const char* path, u32 slots) {
|
||||
ShowPrompt(false, "Out of memory.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
u32 ret = SafeInstallFirmBuffered(path, slots, buffer, FIRM_MAX_SIZE);
|
||||
free(buffer);
|
||||
return ret;
|
||||
@ -609,29 +609,29 @@ u32 SafeInstallFirm(const char* path, u32 slots) {
|
||||
u32 SafeInstallKeyDb(const char* path) {
|
||||
static const u8 perfect_sha[] = { KEYDB_PERFECT_HASH };
|
||||
u8 keydb[KEYDB_PERFECT_SIZE] __attribute__((aligned(4)));
|
||||
|
||||
|
||||
char pathstr[32 + 1]; // truncated path string
|
||||
TruncateString(pathstr, path, 32, 8);
|
||||
|
||||
|
||||
// already installed?
|
||||
if ((ReadNandBytes(keydb, SECTOR_KEYDB*0x200, KEYDB_PERFECT_SIZE, 0xFF, NAND_SYSNAND) == 0) &&
|
||||
(sha_cmp(perfect_sha, keydb, KEYDB_PERFECT_SIZE, SHA256_MODE) == 0)) {
|
||||
ShowPrompt(false, "Perfect " KEYDB_NAME " is already installed!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// check input path...
|
||||
if ((fvx_qread(path, keydb, 0, KEYDB_PERFECT_SIZE, NULL) != FR_OK) ||
|
||||
(sha_cmp(perfect_sha, keydb, KEYDB_PERFECT_SIZE, SHA256_MODE) != 0)) {
|
||||
ShowPrompt(false, "%s\nNot a perfect " KEYDB_NAME " image.\nCannot install to NAND!", pathstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// point of no return, install key database
|
||||
if (WriteNandBytes(keydb, SECTOR_KEYDB*0x200, KEYDB_PERFECT_SIZE, 0xFF, NAND_SYSNAND) != 0) {
|
||||
ShowPrompt(false, "%s\nFailed writing " KEYDB_NAME " to NAND!", pathstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ void Paint9_DrawBrush(u16 px, u16 py, u32 color_fg, u32 color_bg, u32 id) {
|
||||
// https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C
|
||||
void Paint9_DrawLine(u16 px0, u16 py0, u16 px1, u16 py1, u32 color_fg, u32 id) {
|
||||
const int dx = abs((s16) px1-px0), sx = (px0 < px1) ? 1 : -1;
|
||||
const int dy = abs((s16) py1-py0), sy = (py0 < py1) ? 1 : -1;
|
||||
const int dy = abs((s16) py1-py0), sy = (py0 < py1) ? 1 : -1;
|
||||
int err = ((dx > dy) ? dx : -dy) / 2;
|
||||
|
||||
while (true) {
|
||||
|
@ -232,7 +232,7 @@ static inline bool isntrboot(void) {
|
||||
|
||||
static inline u32 strntohex(const char* str, u8* hex, u32 len) {
|
||||
if (!len) {
|
||||
len = strlen(str);
|
||||
len = strlen(str);
|
||||
if (len%1) return 0;
|
||||
else len >>= 1;
|
||||
} else if (len*2 != strnlen(str, (len*2)+1)) {
|
||||
@ -285,28 +285,28 @@ static inline u32 line_len(const char* text, u32 len, u32 ww, const char* line,
|
||||
}
|
||||
|
||||
static inline char* line_seek(const char* text, u32 len, u32 ww, const char* line, int add) {
|
||||
// safety checks /
|
||||
// safety checks /
|
||||
if (line < text) return NULL;
|
||||
if ((line >= (text + len)) && (add >= 0)) return (char*) line;
|
||||
|
||||
|
||||
if (!ww) { // non wordwrapped mode
|
||||
char* lf = ((char*) line - 1);
|
||||
|
||||
|
||||
// ensure we are at the start of the line
|
||||
while ((lf > text) && (*lf != '\n')) lf--;
|
||||
|
||||
|
||||
// handle backwards search
|
||||
for (; (add < 0) && (lf >= text); add++)
|
||||
for (lf--; (lf >= text) && (*lf != '\n'); lf--);
|
||||
|
||||
|
||||
// handle forwards search
|
||||
for (; (add > 0) && (lf < text + len); add--)
|
||||
for (lf++; (lf < text + len) && (*lf != '\n'); lf++);
|
||||
|
||||
|
||||
return lf + 1;
|
||||
} else { // wordwrapped mode
|
||||
char* l0 = (char*) line;
|
||||
|
||||
|
||||
// handle forwards wordwrapped search
|
||||
for (; (add > 0) && (l0 < text + len); add--) {
|
||||
char* eol = NULL;
|
||||
@ -314,7 +314,7 @@ static inline char* line_seek(const char* text, u32 len, u32 ww, const char* lin
|
||||
if (eol || !llenww) l0 = line_seek(text, len, 0, l0, 1);
|
||||
else l0 += llenww;
|
||||
}
|
||||
|
||||
|
||||
// handle backwards wordwrapped search
|
||||
while ((add < 0) && (l0 > text)) {
|
||||
char* l1 = line_seek(text, len, 0, l0, -1);
|
||||
@ -331,19 +331,19 @@ static inline char* line_seek(const char* text, u32 len, u32 ww, const char* lin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return l0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline u32 get_lno(const char* text, u32 len, const char* line) {
|
||||
u32 lno = 1;
|
||||
|
||||
|
||||
for (u32 i = 0; i < len; i++) {
|
||||
if (line <= text + i) return lno;
|
||||
else if (text[i] == '\n') lno++;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -369,11 +369,11 @@ void set_preview(const char* name, const char* content) {
|
||||
|
||||
char* set_var(const char* name, const char* content) {
|
||||
Gm9ScriptVar* vars = (Gm9ScriptVar*) var_buffer;
|
||||
|
||||
|
||||
if ((strnlen(name, _VAR_NAME_LEN) > (_VAR_NAME_LEN-1)) || (strnlen(content, _VAR_CNT_LEN) > (_VAR_CNT_LEN-1)) ||
|
||||
(strchr(name, '[') || strchr(name, ']')))
|
||||
return NULL;
|
||||
|
||||
|
||||
u32 n_var = 0;
|
||||
for (Gm9ScriptVar* var = vars; n_var < _VAR_MAX_BUFF; n_var++, var++)
|
||||
if (!*(var->name) || (strncmp(var->name, name, _VAR_NAME_LEN) == 0)) break;
|
||||
@ -383,10 +383,10 @@ char* set_var(const char* name, const char* content) {
|
||||
strncpy(vars[n_var].content, content, _VAR_CNT_LEN);
|
||||
vars[n_var].content[_VAR_CNT_LEN - 1] = '\0';
|
||||
if (!n_var) *(vars[n_var].content) = '\0'; // NULL var
|
||||
|
||||
|
||||
// update preview stuff
|
||||
set_preview(name, content);
|
||||
|
||||
|
||||
return vars[n_var].content;
|
||||
}
|
||||
|
||||
@ -397,18 +397,18 @@ void upd_var(const char* name) {
|
||||
u8 secinfo_data[1 + 1 + 16] = { 0 };
|
||||
char* env_serial = (char*) secinfo_data + 2;
|
||||
char env_region[3 + 1] = { 0 };
|
||||
|
||||
|
||||
snprintf(env_region, 0x4, "UNK");
|
||||
if ((FileGetData("1:/rw/sys/SecureInfo_A", secinfo_data, 0x11, 0x100) != 0x11) &&
|
||||
(FileGetData("1:/rw/sys/SecureInfo_B", secinfo_data, 0x11, 0x100) != 0x11))
|
||||
snprintf(env_serial, 0xF, "UNKNOWN");
|
||||
else if (*secinfo_data < SMDH_NUM_REGIONS)
|
||||
strncpy(env_region, g_regionNamesShort[*secinfo_data], countof(env_region) - 1);
|
||||
|
||||
|
||||
set_var("SERIAL", env_serial);
|
||||
set_var("REGION", env_region);
|
||||
}
|
||||
|
||||
|
||||
// device sysnand / emunand id0
|
||||
for (u32 emu = 0; emu <= 1; emu++) {
|
||||
const char* env_id0_name = (emu) ? "EMUID0" : "SYSID0";
|
||||
@ -425,7 +425,7 @@ void upd_var(const char* name) {
|
||||
set_var(env_id0_name, env_id0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// datestamp & timestamp
|
||||
if (!name || (strncmp(name, "DATESTAMP", _VAR_NAME_LEN) == 0) || (strncmp(name, "TIMESTAMP", _VAR_NAME_LEN) == 0)) {
|
||||
DsTime dstime;
|
||||
@ -449,7 +449,7 @@ void upd_var(const char* name) {
|
||||
|
||||
char* get_var(const char* name, char** endptr) {
|
||||
Gm9ScriptVar* vars = (Gm9ScriptVar*) var_buffer;
|
||||
|
||||
|
||||
u32 name_len = 0;
|
||||
char* pname = NULL;
|
||||
if (!endptr) { // no endptr, varname is verbatim
|
||||
@ -462,26 +462,26 @@ char* get_var(const char* name, char** endptr) {
|
||||
if ((name_len >= _VAR_NAME_LEN) || !pname[name_len]) return NULL;
|
||||
*endptr = pname + name_len + 1;
|
||||
}
|
||||
|
||||
|
||||
char vname[_VAR_NAME_LEN];
|
||||
strncpy(vname, pname, name_len);
|
||||
vname[name_len] = '\0';
|
||||
upd_var(vname); // handle dynamic env vars
|
||||
|
||||
|
||||
u32 n_var = 0;
|
||||
for (Gm9ScriptVar* var = vars; n_var < _VAR_MAX_BUFF; n_var++, var++) {
|
||||
if (!*(var->name) || (strncmp(var->name, vname, _VAR_NAME_LEN) == 0)) break;
|
||||
}
|
||||
|
||||
|
||||
if (n_var >= _VAR_MAX_BUFF || !*(vars[n_var].name)) n_var = 0;
|
||||
|
||||
|
||||
return vars[n_var].content;
|
||||
}
|
||||
|
||||
bool init_vars(const char* path_script) {
|
||||
// reset var buffer
|
||||
memset(var_buffer, 0x00, sizeof(Gm9ScriptVar) * _VAR_MAX_BUFF);
|
||||
|
||||
|
||||
// current path
|
||||
char curr_dir[_VAR_CNT_LEN];
|
||||
if (path_script) {
|
||||
@ -490,9 +490,9 @@ bool init_vars(const char* path_script) {
|
||||
char* slash = strrchr(curr_dir, '/');
|
||||
if (slash) *slash = '\0';
|
||||
} else strncpy(curr_dir, "(null)", _VAR_CNT_LEN - 1);
|
||||
|
||||
|
||||
// set env vars
|
||||
set_var("NULL", ""); // this one is special and should not be changed later
|
||||
set_var("NULL", ""); // this one is special and should not be changed later
|
||||
set_var("CURRDIR", curr_dir); // script path, never changes
|
||||
set_var("GM9OUT", OUTPUT_PATH); // output path, never changes
|
||||
set_var("HAX", IS_UNLOCKED ? (isntrboot() ? "ntrboot" : "sighax") : ""); // type of hax running from
|
||||
@ -501,17 +501,17 @@ bool init_vars(const char* path_script) {
|
||||
char* ptr = set_var("GM9VER", VERSION); // GodMode9 version, truncated below
|
||||
while (*(ptr++) != '\0') if (*ptr == '-') *ptr = '\0';
|
||||
upd_var(NULL); // set all dynamic environment vars
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool expand_arg(char* argex, const char* arg, u32 len) {
|
||||
char* out = argex;
|
||||
|
||||
|
||||
for (char* in = (char*) arg; in - arg < (int) len; in++) {
|
||||
u32 out_len = out - argex;
|
||||
if (out_len >= (_ARG_MAX_LEN-1)) return false; // maximum arglen reached
|
||||
|
||||
|
||||
if (*in == '\\') { // escape line breaks
|
||||
if (*(++in) == 'n') *(out++) = '\n';
|
||||
else {
|
||||
@ -529,20 +529,20 @@ bool expand_arg(char* argex, const char* arg, u32 len) {
|
||||
} else *(out++) = *in;
|
||||
}
|
||||
*out = '\0';
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
cmd_id get_cmd_id(char* cmd, u32 len, u32 flags, u32 argc, char* err_str) {
|
||||
const Gm9ScriptCmd* cmd_entry = NULL;
|
||||
|
||||
|
||||
for (u32 i = 0; i < (sizeof(cmd_list)/sizeof(Gm9ScriptCmd)); i++) {
|
||||
if (strncmp(cmd_list[i].cmd, cmd, len) == 0) {
|
||||
cmd_entry = cmd_list + i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!cmd_entry) {
|
||||
if (err_str) snprintf(err_str, _ERR_STR_LEN, "unknown cmd");
|
||||
} else if (cmd_entry->n_args != argc) {
|
||||
@ -550,13 +550,13 @@ cmd_id get_cmd_id(char* cmd, u32 len, u32 flags, u32 argc, char* err_str) {
|
||||
} else if (~(cmd_entry->allowed_flags|_FLG('o')|_FLG('s')) & flags) {
|
||||
if (err_str) snprintf(err_str, _ERR_STR_LEN, "unrecognized flags");
|
||||
} else return cmd_entry->id;
|
||||
|
||||
|
||||
return CMD_ID_NONE;
|
||||
}
|
||||
|
||||
u32 get_flag(char* str, u32 len, char* err_str) {
|
||||
char flag_char = '\0';
|
||||
|
||||
|
||||
if ((len < 2) || (*str != '-')) flag_char = '\0';
|
||||
else if (len == 2) flag_char = str[1];
|
||||
else if (strncmp(str, "--all", len) == 0) flag_char = 'a';
|
||||
@ -577,23 +577,23 @@ u32 get_flag(char* str, u32 len, char* err_str) {
|
||||
else if (strncmp(str, "--unequal", len) == 0) flag_char = 'u';
|
||||
else if (strncmp(str, "--overwrite", len) == 0) flag_char = 'w';
|
||||
else if (strncmp(str, "--explorer", len) == 0) flag_char = 'x';
|
||||
|
||||
|
||||
if ((flag_char < 'a') && (flag_char > 'z')) {
|
||||
if (err_str) snprintf(err_str, _ERR_STR_LEN, "illegal flag");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
return _FLG(flag_char);
|
||||
}
|
||||
|
||||
char* get_string(char* ptr, const char* line_end, u32* len, char** next, char* err_str) {
|
||||
char* str = NULL;
|
||||
*len = 0;
|
||||
|
||||
|
||||
// skip whitespaces
|
||||
for (; IS_WHITESPACE(*ptr) && (ptr < line_end); ptr++);
|
||||
if (ptr >= line_end) return (*next = (char*) line_end); // end reached, all whitespaces
|
||||
|
||||
|
||||
// handle string
|
||||
if (*ptr == '\"') { // quotes
|
||||
str = ++ptr;
|
||||
@ -604,11 +604,11 @@ char* get_string(char* ptr, const char* line_end, u32* len, char** next, char* e
|
||||
}
|
||||
*next = ptr + 1;
|
||||
} else { // no quotes, no whitespace
|
||||
str = ptr;
|
||||
str = ptr;
|
||||
for (; !IS_WHITESPACE(*ptr) && (ptr < line_end); ptr++, (*len)++);
|
||||
*next = ptr;
|
||||
}
|
||||
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
@ -618,16 +618,16 @@ char* skip_block(char* ptr, bool ignore_else, bool stop_after_end) {
|
||||
char* line_start = ptr;
|
||||
char* line_end = strchr(ptr, '\n');
|
||||
if (!line_end) line_end = ptr + strlen(ptr);
|
||||
|
||||
|
||||
// grab first string
|
||||
char* str = NULL;
|
||||
u32 str_len = 0;
|
||||
if (!(str = get_string(ptr, line_end, &str_len, &ptr, NULL)) || (str >= line_end)) {
|
||||
// string error or empty line
|
||||
ptr = line_end + 1;
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// check string
|
||||
if (MATCH_STR(str, str_len, _CMD_END)) { // stop at end
|
||||
return line_start; // end of block found
|
||||
@ -638,19 +638,19 @@ char* skip_block(char* ptr, bool ignore_else, bool stop_after_end) {
|
||||
} else if (MATCH_STR(str, str_len, _CMD_IF)) {
|
||||
ptr = line_start = skip_block(line_end + 1, true, false);
|
||||
if (ptr == NULL) return NULL;
|
||||
|
||||
|
||||
line_end = strchr(ptr, '\n');
|
||||
if (!line_end) line_end = ptr + strlen(ptr);
|
||||
|
||||
|
||||
str = get_string(ptr, line_end, &str_len, &ptr, NULL);
|
||||
if (!(MATCH_STR(str, str_len, _CMD_END))) return NULL;
|
||||
if (stop_after_end) return line_end + 1;
|
||||
}
|
||||
|
||||
|
||||
// move on to the next line
|
||||
ptr = line_end + 1;
|
||||
}
|
||||
|
||||
|
||||
// end of block not found
|
||||
return NULL;
|
||||
}
|
||||
@ -661,16 +661,16 @@ char* find_next(char* ptr) {
|
||||
char* line_start = ptr;
|
||||
char* line_end = strchr(ptr, '\n');
|
||||
if (!line_end) line_end = ptr + strlen(ptr);
|
||||
|
||||
|
||||
// grab first string
|
||||
char* str = NULL;
|
||||
u32 str_len = 0;
|
||||
if (!(str = get_string(ptr, line_end, &str_len, &ptr, NULL)) || (str >= line_end)) {
|
||||
// string error or empty line
|
||||
ptr = line_end + 1;
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// check string
|
||||
if (MATCH_STR(str, str_len, _CMD_IF)) { // skip 'if' blocks
|
||||
ptr = skip_block(ptr, true, true);
|
||||
@ -679,11 +679,11 @@ char* find_next(char* ptr) {
|
||||
} else if (MATCH_STR(str, str_len, _CMD_NEXT)) {
|
||||
return line_start;
|
||||
}
|
||||
|
||||
|
||||
// move on to the next line
|
||||
ptr = line_end + 1;
|
||||
}
|
||||
|
||||
|
||||
// 'next' not found
|
||||
return NULL;
|
||||
}
|
||||
@ -692,13 +692,13 @@ char* find_label(const char* label, const char* last_found) {
|
||||
char* script = (char*) script_buffer;
|
||||
char* ptr = script;
|
||||
u32 label_len = strnlen(label, _ARG_MAX_LEN);
|
||||
|
||||
|
||||
if (last_found) {
|
||||
ptr = strchr(last_found, '\n');
|
||||
if (!ptr) return NULL;
|
||||
ptr++;
|
||||
}
|
||||
|
||||
|
||||
char* next = ptr;
|
||||
for (; next && *ptr; ptr = next) {
|
||||
// store line start / get line end
|
||||
@ -706,27 +706,27 @@ char* find_label(const char* label, const char* last_found) {
|
||||
char* line_end = strchr(ptr, '\n');
|
||||
if (!line_end) line_end = ptr + strlen(ptr);
|
||||
next = line_end + 1;
|
||||
|
||||
|
||||
// search for label
|
||||
char* str = NULL;
|
||||
u32 str_len = 0;
|
||||
if (!(str = get_string(ptr, line_end, &str_len, &ptr, NULL))) continue; // string error, ignore line
|
||||
else if (str >= line_end) continue; // empty line
|
||||
|
||||
|
||||
if (*str == '@') {
|
||||
// label found
|
||||
str++; str_len--;
|
||||
|
||||
|
||||
// compare it manually (also check for '*' at end)
|
||||
u32 pdiff = 0;
|
||||
for (; (pdiff < str_len) && (label[pdiff] == str[pdiff]); pdiff++);
|
||||
if ((pdiff < label_len) && (label[pdiff] != '*')) continue; // no match
|
||||
// otherwise: potential regular or wildcard match
|
||||
|
||||
|
||||
// may be a match, see if there are more strings after it
|
||||
if (!(str = get_string(ptr, line_end, &str_len, &ptr, NULL))) continue; // string error, ignore line
|
||||
else if ((str < line_end) && (*str != '#')) continue; // neither end of line nor comment
|
||||
|
||||
|
||||
return line_start; // match found
|
||||
} else if (MATCH_STR(str, str_len, _CMD_IF)) {
|
||||
next = skip_block(line_start, true, true);
|
||||
@ -734,7 +734,7 @@ char* find_label(const char* label, const char* last_found) {
|
||||
next = find_next(line_start);
|
||||
} // otherwise: irrelevant line
|
||||
}
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -744,13 +744,13 @@ bool for_handler(char* path, const char* dir, const char* pattern, bool recursiv
|
||||
static char ldir[256];
|
||||
static char lpattern[64];
|
||||
static bool rec = false;
|
||||
|
||||
|
||||
if (!path && !dir && !pattern) { // close all dirs
|
||||
while (dp >= fdir) fvx_closedir(dp--);
|
||||
dp = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (dir) { // open a dir
|
||||
snprintf(lpattern, 64, "%s", pattern);
|
||||
snprintf(ldir, 256, "%s", dir);
|
||||
@ -769,14 +769,14 @@ bool for_handler(char* path, const char* dir, const char* pattern, bool recursiv
|
||||
if (!slash) return false;
|
||||
*slash = '\0';
|
||||
}
|
||||
|
||||
|
||||
snprintf(path, 256, "%s/%.254s", ldir, fno.fname);
|
||||
if (rec && (fno.fattrib & AM_DIR) && (dp - fdir < _MAX_FOR_DEPTH - 1)) {
|
||||
if (fvx_opendir(++dp, path) != FR_OK) dp--;
|
||||
else strncpy(ldir, path, 255);
|
||||
}
|
||||
} else return false;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -784,18 +784,18 @@ bool parse_line(const char* line_start, const char* line_end, cmd_id* cmdid, u32
|
||||
char* ptr = (char*) line_start;
|
||||
char* str;
|
||||
u32 len;
|
||||
|
||||
|
||||
// set everything to initial values
|
||||
*cmdid = 0;
|
||||
*flags = 0;
|
||||
*argc = 0;
|
||||
|
||||
|
||||
// search for cmd
|
||||
char* cmd = NULL;
|
||||
u32 cmd_len = 0;
|
||||
if (!(cmd = get_string(ptr, line_end, &cmd_len, &ptr, err_str))) return false; // string error
|
||||
if ((cmd >= line_end) || (*cmd == '#') || (*cmd == '@')) return true; // empty line or comment or label
|
||||
|
||||
|
||||
// special handling for "if", "elif" and "not"
|
||||
if (MATCH_STR(cmd, cmd_len, _CMD_NOT)) {
|
||||
*cmdid = CMD_ID_NOT;
|
||||
@ -807,7 +807,7 @@ bool parse_line(const char* line_start, const char* line_end, cmd_id* cmdid, u32
|
||||
*cmdid = CMD_ID_ELIF;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// got cmd, now parse flags & args
|
||||
while ((str = get_string(ptr, line_end, &len, &ptr, err_str))) {
|
||||
bool in_quotes = ((ptr - str) != (int) len); // hacky
|
||||
@ -825,14 +825,14 @@ bool parse_line(const char* line_start, const char* line_end, cmd_id* cmdid, u32
|
||||
return false; // arg expand failed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// end reached with a failed get_string()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
bool ret = true; // true unless some cmd messes up
|
||||
|
||||
|
||||
// process arg0 @string
|
||||
u64 at_org = 0;
|
||||
u64 sz_org = 0;
|
||||
@ -846,7 +846,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// perform command
|
||||
if (id == CMD_ID_NOT) {
|
||||
// check the argument
|
||||
@ -859,7 +859,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
// "if true" or "if false"
|
||||
skip_state = (strncmp(argv[0], _ARG_TRUE, _ARG_MAX_LEN) == 0) ? 0 : _SKIP_BLOCK;
|
||||
ifcnt++;
|
||||
|
||||
|
||||
if (syntax_error && err_str)
|
||||
snprintf(err_str, _ERR_STR_LEN, "syntax error after 'if'");
|
||||
ret = !syntax_error;
|
||||
@ -871,12 +871,12 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
syntax_error = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// skip state handling, check the argument if required
|
||||
// "if true" or "if false"
|
||||
skip_state = !skip_state ? _SKIP_TILL_END :
|
||||
((strncmp(argv[0], _ARG_TRUE, _ARG_MAX_LEN) == 0) ? 0 : _SKIP_BLOCK);
|
||||
|
||||
|
||||
if (syntax_error && err_str)
|
||||
snprintf(err_str, _ERR_STR_LEN, "syntax error after 'elif'");
|
||||
ret = !syntax_error;
|
||||
@ -888,10 +888,10 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
syntax_error = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// turn the skip state
|
||||
skip_state = skip_state ? 0 : _SKIP_TILL_END;
|
||||
|
||||
|
||||
ret = true;
|
||||
}
|
||||
else if (id == CMD_ID_END) {
|
||||
@ -901,11 +901,11 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
syntax_error = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// close last "if"
|
||||
skip_state = 0;
|
||||
ifcnt--;
|
||||
|
||||
|
||||
ret = true;
|
||||
}
|
||||
else if (id == CMD_ID_FOR) {
|
||||
@ -959,16 +959,16 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
char* options_jmp[_CHOICE_MAX_N] = { NULL };
|
||||
char options_str[_CHOICE_MAX_N][_CHOICE_STR_LEN+1];
|
||||
u32 options_keys[_CHOICE_MAX_N] = { 0 };
|
||||
|
||||
|
||||
char* ast = strchr(argv[1], '*');
|
||||
char* ptr = NULL;
|
||||
u32 n_opt = 0;
|
||||
while ((ptr = find_label(argv[1], ptr))) {
|
||||
options_jmp[n_opt] = ptr;
|
||||
|
||||
|
||||
while (*(ptr++) != '@');
|
||||
if (ast) ptr += (ast - argv[1]);
|
||||
|
||||
|
||||
char* choice = options_str[n_opt];
|
||||
for (u32 i = 0; i < _CHOICE_STR_LEN; choice[++i] = '\0') {
|
||||
if (IS_WHITESPACE(ptr[i])) break;
|
||||
@ -980,13 +980,13 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
for (; *choice != ' ' && *choice != '\0'; choice++);
|
||||
if (*choice != '\0') *(choice++) = '\0';
|
||||
options_keys[n_opt] = StringToButton(keystr);
|
||||
if (!options_keys[n_opt]) continue;
|
||||
if (!options_keys[n_opt]) continue;
|
||||
}
|
||||
|
||||
options[n_opt] = choice;
|
||||
if (++n_opt >= _CHOICE_MAX_N) break;
|
||||
}
|
||||
|
||||
|
||||
u32 result = (flags & _FLG('k')) ? ShowHotkeyPrompt(n_opt, options, options_keys, "%s", argv[0]) :
|
||||
ShowSelectPrompt(n_opt, options, "%s", argv[0]);
|
||||
|
||||
@ -1039,7 +1039,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
char* var = get_var(argv[2], NULL);
|
||||
strncpy(choice, var, _VAR_CNT_LEN);
|
||||
choice[_VAR_CNT_LEN - 1] = '\0';
|
||||
|
||||
|
||||
char path[_VAR_CNT_LEN];
|
||||
strncpy(path, argv[1], _VAR_CNT_LEN);
|
||||
path[_VAR_CNT_LEN - 1] = '\0';
|
||||
@ -1061,7 +1061,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
ret = FileSelector(choice, argv[0], path, NULL, NO_FILES | SELECT_DIRS, (flags & _FLG('x')));
|
||||
if (err_str) snprintf(err_str, _ERR_STR_LEN, "dirselect abort");
|
||||
}
|
||||
|
||||
|
||||
if (ret) {
|
||||
ret = set_var(argv[2], choice);
|
||||
if (err_str) snprintf(err_str, _ERR_STR_LEN, "var fail");
|
||||
@ -1075,14 +1075,14 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
char str[_ARG_MAX_LEN];
|
||||
strncpy(str, argv[1], _ARG_MAX_LEN);
|
||||
str[_ARG_MAX_LEN - 1] = '\0';
|
||||
|
||||
|
||||
ret = false;
|
||||
if (strlen(argv[2]) == 1) { // argv[2] must be one char
|
||||
char* found;
|
||||
if (flags & _FLG('f')) found = strchr(str, *argv[2]);
|
||||
else found = strrchr(str, *argv[2]);
|
||||
if (!found && err_str) snprintf(err_str, _ERR_STR_LEN, "char not found");
|
||||
|
||||
|
||||
if (found) {
|
||||
if (flags & _FLG('b')) {
|
||||
*found = '\0';
|
||||
@ -1096,7 +1096,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
char str[_ARG_MAX_LEN];
|
||||
strncpy(str, argv[1], _ARG_MAX_LEN);
|
||||
str[_ARG_MAX_LEN - 1] = '\0';
|
||||
|
||||
|
||||
if (strnlen(argv[2], _ARG_MAX_LEN) != 2) {
|
||||
if (err_str) snprintf(err_str, _ERR_STR_LEN, "argv[2] must be 2 chars");
|
||||
ret = false;
|
||||
@ -1480,7 +1480,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
||||
ret = false;
|
||||
if (err_str) snprintf(err_str, _ERR_STR_LEN, "unknown error");
|
||||
}
|
||||
|
||||
|
||||
if (ret && err_str) snprintf(err_str, _ERR_STR_LEN, "command success");
|
||||
return ret;
|
||||
}
|
||||
@ -1494,18 +1494,18 @@ bool run_line(const char* line_start, const char* line_end, u32* flags, char* er
|
||||
// set up argv array
|
||||
for (u32 i = 0; i < _MAX_ARGS; i++)
|
||||
argv[i] = args[i];
|
||||
|
||||
|
||||
// flags handling (if no pointer given)
|
||||
u32 lflags;
|
||||
if (!flags) flags = &lflags;
|
||||
*flags = 0;
|
||||
|
||||
|
||||
// parse current line, grab cmd / flags / args
|
||||
if (!parse_line(line_start, line_end, &cmdid, flags, &argc, argv, err_str)) {
|
||||
syntax_error = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// control flow command handling
|
||||
// block out of control flow commands
|
||||
if (if_cond && IS_CTRLFLOW_CMD(cmdid)) {
|
||||
@ -1513,36 +1513,36 @@ bool run_line(const char* line_start, const char* line_end, u32* flags, char* er
|
||||
syntax_error = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// shortcuts for "elif" / "else"
|
||||
if (((cmdid == CMD_ID_ELIF) || (cmdid == CMD_ID_ELSE)) && !skip_state) {
|
||||
skip_state = _SKIP_TILL_END;
|
||||
cmdid = 0;
|
||||
}
|
||||
|
||||
|
||||
// handle "if" / "elif" / "not"
|
||||
if ((cmdid == CMD_ID_IF) || (cmdid == CMD_ID_ELIF) || (cmdid == CMD_ID_NOT)) {
|
||||
// set defaults
|
||||
argc = 1;
|
||||
strncpy(argv[0], _ARG_FALSE, _ARG_MAX_LEN - 1);
|
||||
|
||||
|
||||
// skip to behind the command
|
||||
char* line_start_next = (char*) line_start;
|
||||
for (; IS_WHITESPACE(*line_start_next); line_start_next++);
|
||||
for (; *line_start_next && !IS_WHITESPACE(*line_start_next); line_start_next++);
|
||||
|
||||
|
||||
// run condition, take over result
|
||||
if (run_line(line_start_next, line_end, flags, err_str, true))
|
||||
strncpy(argv[0], _ARG_TRUE, _ARG_MAX_LEN - 1);
|
||||
}
|
||||
|
||||
|
||||
// run the command (if available)
|
||||
if (cmdid && !run_cmd(cmdid, *flags, argv, err_str)) {
|
||||
char* msg_fail = get_var("ERRORMSG", NULL);
|
||||
if (msg_fail && *msg_fail) *err_str = '\0'; // use custom error message
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// success if we arrive here
|
||||
return true;
|
||||
}
|
||||
@ -1581,7 +1581,7 @@ void MemTextView(const char* text, u32 len, char* line0, int off_disp, int lno,
|
||||
if (ncpy > TV_LLEN_DISP) ncpy = TV_LLEN_DISP;
|
||||
bool al = !ww && off_disp && (ptr != ptr_next);
|
||||
bool ar = !ww && (llen > off_disp + TV_LLEN_DISP);
|
||||
|
||||
|
||||
// set text color / find start of comment of scripts
|
||||
u32 color_text = (nln == mno) ? script_color_active : (is_script) ? script_color_code : (u32) COLOR_TVTEXT;
|
||||
int cmt_start = TV_LLEN_DISP; // start of comment in current displayed line (may be negative)
|
||||
@ -1591,14 +1591,14 @@ void MemTextView(const char* text, u32 len, char* line0, int off_disp, int lno,
|
||||
cmt_start = (hash - ptr) - off_disp;
|
||||
}
|
||||
if (cmt_start <= 0) color_text = script_color_comment;
|
||||
|
||||
|
||||
// build text string
|
||||
snprintf(txtstr, TV_LLEN_DISP + 1, "%-*.*s", (int) TV_LLEN_DISP, (int) TV_LLEN_DISP, "");
|
||||
if (ncpy) memcpy(txtstr, ptr + off_disp, ncpy);
|
||||
for (char* d = txtstr; *d; d++) if (*d < ' ') *d = ' ';
|
||||
if (al) memcpy(txtstr + p_al, al_str, strnlen(al_str, 16));
|
||||
if (ar) memcpy(txtstr + p_ar, ar_str, strnlen(ar_str, 16));
|
||||
|
||||
|
||||
// draw line number & text
|
||||
DrawString(TOP_SCREEN, txtstr, x_txt, y, color_text, COLOR_STD_BG, false);
|
||||
if (TV_LNOS > 0) { // line number
|
||||
@ -1606,17 +1606,17 @@ void MemTextView(const char* text, u32 len, char* line0, int off_disp, int lno,
|
||||
DrawStringF(TOP_SCREEN, x_lno, y, ((ptr == text) || (*(ptr-1) == '\n')) ? COLOR_TVOFFS : COLOR_TVOFFSL, COLOR_STD_BG, "%0*lu", TV_LNOS, nln);
|
||||
else DrawStringF(TOP_SCREEN, x_lno, y, COLOR_TVOFFSL, COLOR_STD_BG, "%*.*s", TV_LNOS, TV_LNOS, " ");
|
||||
}
|
||||
|
||||
|
||||
// colorize comment if is_script
|
||||
if ((cmt_start > 0) && ((u32) cmt_start < TV_LLEN_DISP)) {
|
||||
memset(txtstr, ' ', cmt_start);
|
||||
DrawString(TOP_SCREEN, txtstr, x_txt, y, script_color_comment, COLOR_TRANSPARENT, false);
|
||||
}
|
||||
|
||||
|
||||
// colorize arrows
|
||||
if (al) DrawStringF(TOP_SCREEN, x_al, y, COLOR_TVOFFS, COLOR_TRANSPARENT, al_str);
|
||||
if (ar) DrawStringF(TOP_SCREEN, x_ar, y, COLOR_TVOFFS, COLOR_TRANSPARENT, ar_str);
|
||||
|
||||
|
||||
// advance pointer / line number
|
||||
for (char* c = ptr; c < ptr_next; c++) if (*c == '\n') ++nln;
|
||||
ptr = ptr_next;
|
||||
@ -1664,7 +1664,7 @@ bool MemTextViewer(const char* text, u32 len, u32 start, bool as_script) {
|
||||
while (true) {
|
||||
// display text on screen
|
||||
MemTextView(text, len, line0, off_disp, lcurr, ww, 0, as_script);
|
||||
|
||||
|
||||
// handle user input
|
||||
u32 pad_state = InputWait(0);
|
||||
char* line0_next = line0;
|
||||
@ -1683,7 +1683,7 @@ bool MemTextViewer(const char* text, u32 len, u32 start, bool as_script) {
|
||||
ww = ww ? 0 : TV_LLEN_DISP;
|
||||
line0_next = line_seek(text, len, ww, line0, 0);
|
||||
} else if (pad_state & (BUTTON_B|BUTTON_START)) break;
|
||||
|
||||
|
||||
// check for problems, apply changes
|
||||
if (!ww && (line0_next > llast_nww)) line0_next = llast_nww;
|
||||
else if (ww && (line0_next > llast_ww)) line0_next = llast_ww;
|
||||
@ -1697,10 +1697,10 @@ bool MemTextViewer(const char* text, u32 len, u32 start, bool as_script) {
|
||||
if (off_disp + TV_LLEN_DISP > llen_max) off_disp = llen_max - TV_LLEN_DISP;
|
||||
if ((off_disp < 0) || ww) off_disp = 0;
|
||||
}
|
||||
|
||||
|
||||
// clear screens
|
||||
ClearScreenF(true, true, COLOR_STD_BG);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1711,17 +1711,17 @@ bool MemToCViewer(const char* text, u32 len, const char* title) {
|
||||
char* captions[max_captions];
|
||||
u32 lineno[max_captions];
|
||||
u32 ww = TV_LLEN_DISP;
|
||||
|
||||
|
||||
// check if this really is text
|
||||
if (!ValidateText(text, len)) {
|
||||
ShowPrompt(false, "Error: Invalid text data");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// clear screens / view start of readme on top
|
||||
ClearScreenF(true, true, COLOR_STD_BG);
|
||||
MemTextView(text, len, (char*) text, 0, 1, ww, 0, false);
|
||||
|
||||
|
||||
// parse text for markdown captions
|
||||
u32 n_captions = 0;
|
||||
char* ptr = (char*) text;
|
||||
@ -1735,7 +1735,7 @@ bool MemToCViewer(const char* text, u32 len, const char* title) {
|
||||
}
|
||||
ptr = ptr_next;
|
||||
}
|
||||
|
||||
|
||||
int cursor = -1;
|
||||
while (true) {
|
||||
// display ToC
|
||||
@ -1756,7 +1756,7 @@ bool MemToCViewer(const char* text, u32 len, const char* title) {
|
||||
"%*.*s", len, len, caption);
|
||||
y0 += FONT_HEIGHT_EXT + (2*TV_VPAD);
|
||||
}
|
||||
|
||||
|
||||
// handle user input
|
||||
u32 pad_state = InputWait(0);
|
||||
if ((cursor >= 0) && (pad_state & BUTTON_A)) {
|
||||
@ -1772,10 +1772,10 @@ bool MemToCViewer(const char* text, u32 len, const char* title) {
|
||||
MemTextView(text, len, captions[cursor], 0, lineno[cursor], ww, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// clear screens
|
||||
ClearScreenF(true, true, COLOR_STD_BG);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1802,29 +1802,29 @@ bool FileTextViewer(const char* path, bool as_script) {
|
||||
bool ExecuteGM9Script(const char* path_script) {
|
||||
char path_str[32+1];
|
||||
TruncateString(path_str, path_script, 32, 12);
|
||||
|
||||
|
||||
|
||||
|
||||
// reset control flow global vars
|
||||
ifcnt = 0;
|
||||
jump_ptr = NULL;
|
||||
for_ptr = NULL;
|
||||
skip_state = 0;
|
||||
syntax_error = false;
|
||||
|
||||
|
||||
|
||||
|
||||
// allocate && check memory
|
||||
var_buffer = (void*) malloc(sizeof(Gm9ScriptVar) * _VAR_MAX_BUFF);
|
||||
script_buffer = (void*) malloc(SCRIPT_MAX_SIZE);
|
||||
char* script = (char*) script_buffer;
|
||||
char* ptr = script;
|
||||
|
||||
|
||||
if (!var_buffer || !script_buffer) {
|
||||
if (var_buffer) free(var_buffer);
|
||||
if (script_buffer) free(script_buffer);
|
||||
ShowPrompt(false, "Out of memory.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// fetch script from path
|
||||
u32 script_size = FileGetData(path_script, (u8*) script, SCRIPT_MAX_SIZE, 0);
|
||||
if (!script_size || (script_size >= SCRIPT_MAX_SIZE)) {
|
||||
@ -1832,13 +1832,13 @@ bool ExecuteGM9Script(const char* path_script) {
|
||||
free(script_buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
char* end = script + script_size;
|
||||
*end = '\0';
|
||||
|
||||
|
||||
// initialise variables
|
||||
init_vars(path_script);
|
||||
|
||||
|
||||
// setup script preview (only if used)
|
||||
u32 preview_mode_local = 0;
|
||||
if (MAIN_SCREEN != TOP_SCREEN) {
|
||||
@ -1848,7 +1848,7 @@ bool ExecuteGM9Script(const char* path_script) {
|
||||
script_color_comment = COLOR_TVCMT;
|
||||
script_color_code = COLOR_TVCMD;
|
||||
}
|
||||
|
||||
|
||||
// script execute loop
|
||||
u32 lno = 1;
|
||||
bool result = true;
|
||||
@ -1905,17 +1905,17 @@ bool ExecuteGM9Script(const char* path_script) {
|
||||
MemTextView(script, script_size, script, 0, 1, 0, lno, true);
|
||||
} else {
|
||||
char* ptr_view = line_seek(script, script_size, 0, ptr, -(TV_NLIN_DISP/2));
|
||||
u32 lno_view = lno - (TV_NLIN_DISP/2);
|
||||
u32 lno_view = lno - (TV_NLIN_DISP/2);
|
||||
MemTextView(script, script_size, ptr_view, 0, lno_view, 0, lno, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// run command
|
||||
char err_str[_ERR_STR_LEN+1] = { 0 };
|
||||
result = run_line(ptr, line_end, &flags, err_str, false);
|
||||
|
||||
|
||||
|
||||
|
||||
// skip state handling
|
||||
char* skip_ptr = ptr;
|
||||
if ((skip_state == _SKIP_BLOCK) || (skip_state == _SKIP_TILL_END)) {
|
||||
@ -1942,12 +1942,12 @@ bool ExecuteGM9Script(const char* path_script) {
|
||||
}
|
||||
skip_state = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (!result) { // error handling
|
||||
if (syntax_error) // severe error, can't continue
|
||||
flags &= ~(_FLG('o')|_FLG('s')); // never silent or optional
|
||||
|
||||
|
||||
if (!(flags & _FLG('s'))) { // not silent
|
||||
if (!*err_str) {
|
||||
char* msg_fail = get_var("ERRORMSG", NULL);
|
||||
@ -1970,7 +1970,7 @@ bool ExecuteGM9Script(const char* path_script) {
|
||||
break;
|
||||
} else result = true; // set back the result otherwise
|
||||
}
|
||||
|
||||
|
||||
// reposition pointer
|
||||
if (skip_ptr != ptr) {
|
||||
ptr = skip_ptr;
|
||||
@ -1987,8 +1987,8 @@ bool ExecuteGM9Script(const char* path_script) {
|
||||
lno++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (result) { // all fine(?) up to this point
|
||||
if (ifcnt) { // check for unresolved 'if'
|
||||
ShowPrompt(false, "%s\nend of script: unresolved 'if'", path_str);
|
||||
@ -2000,13 +2000,13 @@ bool ExecuteGM9Script(const char* path_script) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (result) { // success message if applicable
|
||||
char* msg_okay = get_var("SUCCESSMSG", NULL);
|
||||
if (msg_okay && *msg_okay) ShowPrompt(false, "%s", msg_okay);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
free(var_buffer);
|
||||
free(script_buffer);
|
||||
return result;
|
||||
|
@ -276,16 +276,16 @@ void GetSysInfo_Movable(SysInfo* info, char nand_drive) {
|
||||
strncpy(info->friendcodeseed, "<unknown>", countof("<unknown>"));
|
||||
strncpy(info->movablekeyy, "<unknown>", countof("<unknown>"));
|
||||
strncpy(info->nand_id0, "<unknown>", countof("<unknown>"));
|
||||
|
||||
|
||||
if (fvx_qread(path, &data, 0, 0x120 /* sizeof(data) */, NULL) != FR_OK) // whatever, we don't need the last 0x20 byte here
|
||||
return;
|
||||
|
||||
|
||||
// The LocalFriendCodeSeed.
|
||||
snprintf(info->friendcodeseed, 16 + 1, "%016llX", getbe64(data.codeseed_data.codeseed));
|
||||
|
||||
|
||||
// The Movable KeyY
|
||||
snprintf(info->movablekeyy, 32 + 1, "%s%016llX", info->friendcodeseed, getbe64(data.keyy_high));
|
||||
|
||||
|
||||
// SysNAND ID0
|
||||
unsigned int sha256sum[8];
|
||||
sha_quick(sha256sum, data.codeseed_data.codeseed, 16, SHA256_MODE);
|
||||
@ -296,17 +296,17 @@ void GetSysInfo_Movable(SysInfo* info, char nand_drive) {
|
||||
// Read sdmmc.
|
||||
void GetSysInfo_SDMMC(SysInfo* info, char nand_drive) {
|
||||
(void) nand_drive;
|
||||
|
||||
|
||||
u8 nand_cid[16] = { 0 };
|
||||
u8 sd_cid[16] = { 0 };
|
||||
|
||||
|
||||
strncpy(info->nand_cid, "<unknown>", countof("<unknown>"));
|
||||
strncpy(info->sd_cid, "<unknown>", countof("<unknown>"));
|
||||
strncpy(info->nand_id1, "<unknown>", countof("<unknown>"));
|
||||
|
||||
|
||||
sdmmc_get_cid(1, (u32*) (void*) nand_cid);
|
||||
snprintf(info->nand_cid, 32 + 1, "%016llX%016llX", getbe64(nand_cid), getbe64(nand_cid+8));
|
||||
|
||||
|
||||
sdmmc_get_cid(0, (u32*) (void*) sd_cid);
|
||||
snprintf(info->sd_cid, 32 + 1, "%016llX%016llX", getbe64(sd_cid), getbe64(sd_cid+8));
|
||||
snprintf(info->nand_id1, 32 + 1, "%08lX%08lX%08lX%08lX",
|
||||
|
@ -90,9 +90,9 @@ u64 InitVBDRIDrive(void) { // prerequisite: .db file mounted as virtual diff ima
|
||||
u64 mount_state = CheckVDisaDiffDrive();
|
||||
if (!(mount_state & SYS_DIFF)) return 0;
|
||||
is_tickdb = (mount_state & SYS_TICKDB);
|
||||
|
||||
|
||||
DeinitVBDRIDrive();
|
||||
|
||||
|
||||
num_entries = min((is_tickdb ? GetNumTickets(PART_PATH) : GetNumTitleInfoEntries(PART_PATH)) + 1, VBDRI_MAX_ENTRIES);
|
||||
title_ids = (u8*) malloc(num_entries * 8);
|
||||
if (!title_ids ||
|
||||
@ -100,12 +100,12 @@ u64 InitVBDRIDrive(void) { // prerequisite: .db file mounted as virtual diff ima
|
||||
DeinitVBDRIDrive();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if (!is_tickdb && ((cached_entry = malloc(sizeof(TitleInfoEntry))) == NULL)) {
|
||||
DeinitVBDRIDrive();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
return mount_state;
|
||||
}
|
||||
|
||||
@ -122,25 +122,25 @@ bool ReadVBDRIDir(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
if (vdir->flags & VFLAG_TICKDIR) { // ticket dir
|
||||
if (!is_tickdb || (!tick_info && !SortVBDRITickets()))
|
||||
return false;
|
||||
|
||||
|
||||
while (++vdir->index < (int) num_entries) {
|
||||
u32 type = tick_info[vdir->index].type;
|
||||
u64 tid = getbe64(title_ids + (vdir->index * 8));
|
||||
|
||||
if ((tid == 0) || !(
|
||||
|
||||
if ((tid == 0) || !(
|
||||
((vdir->flags & VFLAG_ESHOP) && (type == 0)) ||
|
||||
((vdir->flags & VFLAG_HOMEBREW) && (type == 1)) ||
|
||||
((vdir->flags & VFLAG_SYSTEM) && (type == 2)) ||
|
||||
((vdir->flags & VFLAG_UNKNOWN) && (type == 3))))
|
||||
continue;
|
||||
|
||||
|
||||
memset(vfile, 0, sizeof(VirtualFile));
|
||||
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;
|
||||
vfile->flags = (vdir->flags | VFLAG_DELETABLE) & ~VFLAG_DIR;
|
||||
|
||||
|
||||
return true; // found
|
||||
}
|
||||
} else { // root dir
|
||||
@ -163,17 +163,17 @@ bool ReadVBDRIDir(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
vfile->size = sizeof(TitleInfoEntry);
|
||||
vfile->keyslot = 0xFF;
|
||||
vfile->flags = (vdir->flags | VFLAG_DELETABLE) & ~VFLAG_DIR;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetNewVBDRIFile(VirtualFile* vfile, VirtualDir* vdir, const char* path) {
|
||||
|
||||
|
||||
size_t path_len = strlen(path), buf_len = (is_tickdb ? NAME_TIK_LEN : NAME_TIE_LEN) + 2;
|
||||
u64 tid;
|
||||
u32 console_id;
|
||||
@ -181,8 +181,8 @@ bool GetNewVBDRIFile(VirtualFile* vfile, VirtualDir* vdir, const char* path) {
|
||||
|
||||
char buf[buf_len];
|
||||
strcpy(buf, path + path_len - buf_len + 1);
|
||||
|
||||
|
||||
|
||||
|
||||
if (( is_tickdb && (sscanf(buf, "/" NAME_TIK "%c", &tid, &console_id, &c) != 2)) ||
|
||||
(!is_tickdb && (sscanf(buf, "/" NAME_TIE "%c", &tid, &c) != 1)) ||
|
||||
(tid == 0))
|
||||
@ -193,15 +193,15 @@ bool GetNewVBDRIFile(VirtualFile* vfile, VirtualDir* vdir, const char* path) {
|
||||
for (u32 i = 0; i < num_entries; i++) {
|
||||
if ((entry_index == -1) && (*((u64*)(void*)(title_ids + 8 * i)) == 0))
|
||||
entry_index = i;
|
||||
|
||||
|
||||
if (memcmp(&tid, title_ids + 8 * i, 8) == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (entry_index == -1) {
|
||||
if (num_entries == VBDRI_MAX_ENTRIES)
|
||||
return false;
|
||||
|
||||
|
||||
u32 new_num_entries = min(num_entries + 128, VBDRI_MAX_ENTRIES);
|
||||
u8* new_title_ids = realloc(title_ids, new_num_entries * 8);
|
||||
if (!new_title_ids)
|
||||
@ -212,37 +212,37 @@ bool GetNewVBDRIFile(VirtualFile* vfile, VirtualDir* vdir, const char* path) {
|
||||
return false;
|
||||
tick_info = new_tick_info;
|
||||
}
|
||||
|
||||
|
||||
entry_index = num_entries;
|
||||
num_entries = new_num_entries;
|
||||
title_ids = new_title_ids;
|
||||
|
||||
|
||||
memset(title_ids + entry_index * 8, 0, (num_entries - entry_index) * 8);
|
||||
}
|
||||
|
||||
|
||||
u32 size = is_tickdb ? TICKET_COMMON_SIZE : sizeof(TitleInfoEntry);
|
||||
u8 entry[size];
|
||||
if (is_tickdb)
|
||||
*((u32*)(void*)(entry + 0x2A8)) = 0xAC000000;
|
||||
if ((is_tickdb ? AddTicketToDB(PART_PATH, (u8*)&tid, (Ticket*)(void*)entry, false) :
|
||||
if ((is_tickdb ? AddTicketToDB(PART_PATH, (u8*)&tid, (Ticket*)(void*)entry, false) :
|
||||
AddTitleInfoEntryToDB(PART_PATH, (u8*)&tid, (TitleInfoEntry*)(void*)entry, false)) != 0)
|
||||
return false;
|
||||
|
||||
|
||||
memcpy(title_ids + entry_index * 8, &tid, 8);
|
||||
|
||||
|
||||
if (tick_info) {
|
||||
tick_info[entry_index].type = 3;
|
||||
tick_info[entry_index].size = TICKET_COMMON_SIZE;
|
||||
memset(tick_info[entry_index].console_id, 0, 4);
|
||||
}
|
||||
|
||||
|
||||
memset(vfile, 0, sizeof(VirtualFile));
|
||||
strcpy(vfile->name, buf);
|
||||
vfile->offset = entry_index; // "offset" is the internal buffer index
|
||||
vfile->size = size;
|
||||
vfile->keyslot = 0xFF;
|
||||
vfile->flags = (vdir->flags | VFLAG_DELETABLE) & ~VFLAG_DIR;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -251,14 +251,14 @@ int ReadVBDRIFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count)
|
||||
memcpy(buffer, cached_entry + offset, count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if (is_tickdb && (cache_index != -1))
|
||||
free(cached_entry);
|
||||
if ((is_tickdb ? ReadTicketFromDB(PART_PATH, title_ids + vfile->offset * 8, (Ticket**) &cached_entry) :
|
||||
ReadTitleInfoEntryFromDB(PART_PATH, title_ids + vfile->offset * 8, (TitleInfoEntry*) cached_entry)) != 0)
|
||||
return 1;
|
||||
cache_index = (int) vfile->offset;
|
||||
|
||||
|
||||
memcpy(buffer, cached_entry + offset, count);
|
||||
return 0;
|
||||
}
|
||||
@ -281,25 +281,25 @@ int WriteVBDRIFile(VirtualFile* vfile, const void* buffer, u64 offset, u64 count
|
||||
return 1;
|
||||
}
|
||||
cache_index = (int) vfile->offset;
|
||||
|
||||
|
||||
if (resize) {
|
||||
u8* new_cached_entry = realloc(cached_entry, vfile->size);
|
||||
if (!new_cached_entry) {
|
||||
vfile->size = tick_info[vfile->offset].size;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
cached_entry = new_cached_entry;
|
||||
|
||||
|
||||
if (RemoveTicketFromDB(PART_PATH, title_ids + vfile->offset * 8) != 0) {
|
||||
vfile->size = tick_info[vfile->offset].size;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
memcpy(cached_entry + offset, buffer, count);
|
||||
|
||||
if ((is_tickdb ? AddTicketToDB(PART_PATH, title_ids + vfile->offset * 8, (Ticket*)(void*)cached_entry, true) :
|
||||
|
||||
if ((is_tickdb ? AddTicketToDB(PART_PATH, title_ids + vfile->offset * 8, (Ticket*)(void*)cached_entry, true) :
|
||||
AddTitleInfoEntryToDB(PART_PATH, title_ids + vfile->offset * 8, (TitleInfoEntry*)(void*)cached_entry, true)) != 0) {
|
||||
if (resize) vfile->size = tick_info[vfile->offset].size;
|
||||
if (is_tickdb) {
|
||||
@ -309,13 +309,13 @@ int WriteVBDRIFile(VirtualFile* vfile, const void* buffer, u64 offset, u64 count
|
||||
cache_index = -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
if (resize) tick_info[vfile->offset].size = vfile->size;
|
||||
|
||||
|
||||
if (tick_info && ((offset <= 0x1F1 && offset + count > 0x1F1) || (cached_entry[0x1F1] == 0 && offset <= 0x104 && offset + count > 4)))
|
||||
tick_info[vfile->offset].type = (cached_entry[0x1F1] > 1) ? 3 :
|
||||
tick_info[vfile->offset].type = (cached_entry[0x1F1] > 1) ? 3 :
|
||||
((ValidateTicketSignature((Ticket*)(void*)cached_entry) != 0) ? 1 : ((cached_entry[0x1F1] == 1) ? 2 : 0));
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -333,7 +333,7 @@ int DeleteVBDRIFile(const VirtualFile* vfile) {
|
||||
|
||||
memset(title_ids + vfile->offset * 8, 0, 8);
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -25,14 +25,14 @@ bool ReadVCartDir(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
if ((vdir->index < 0) && !cart_init)
|
||||
InitVCartDrive();
|
||||
if (!cart_init) return false;
|
||||
|
||||
|
||||
const char* ext = (cdata->cart_type & CART_CTR) ? "3ds" : "nds";
|
||||
char name[24];
|
||||
GetCartName(name, cdata);
|
||||
memset(vfile, 0, sizeof(VirtualFile));
|
||||
vfile->keyslot = 0xFF; // unused
|
||||
vfile->flags = VFLAG_READONLY;
|
||||
|
||||
|
||||
while (++vdir->index <= 7) {
|
||||
if ((vdir->index == 0) && (cdata->data_size < FAT_LIMIT)) { // standard full rom
|
||||
snprintf(vfile->name, 32, "%s.%s", name, ext);
|
||||
@ -69,7 +69,7 @@ bool ReadVCartDir(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -31,39 +31,39 @@ static void AlignDisaDiffIvfcRange(DisaDiffIvfcRange* range, u32 log_block_size)
|
||||
static u32 MergeDisaDiffIvfcRange(DisaDiffIvfcRange new_range, DisaDiffIvfcRange* ranges, u32* n_ranges) {
|
||||
const u32 new_end_offset = new_range.offset + new_range.size;
|
||||
bool add_range = true;
|
||||
|
||||
|
||||
if (*n_ranges > MAX_IVFC_RANGES)
|
||||
return 1;
|
||||
|
||||
|
||||
for (u32 i = 0; i < *n_ranges; i++) {
|
||||
u32 end_offset = ranges[i].offset + ranges[i].size;
|
||||
|
||||
|
||||
if (new_range.offset > ranges[i].offset) {
|
||||
if (end_offset >= new_range.offset) {
|
||||
add_range = false;
|
||||
|
||||
|
||||
if (end_offset < new_end_offset) {
|
||||
ranges[i].size += new_end_offset - end_offset;
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
continue;
|
||||
} else if (ranges[i].offset > new_range.offset) {
|
||||
if (new_end_offset >= ranges[i].offset) {
|
||||
add_range = false;
|
||||
|
||||
|
||||
if (new_end_offset < end_offset) {
|
||||
ranges[i].offset = new_range.offset;
|
||||
ranges[i].size = new_range.size + end_offset - new_end_offset;
|
||||
} else {
|
||||
ranges[i] = new_range;
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
continue;
|
||||
} else {
|
||||
add_range = false;
|
||||
@ -71,46 +71,46 @@ static u32 MergeDisaDiffIvfcRange(DisaDiffIvfcRange new_range, DisaDiffIvfcRange
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (add_range) {
|
||||
if (*n_ranges == MAX_IVFC_RANGES - 1)
|
||||
return 1;
|
||||
|
||||
|
||||
ranges[(*n_ranges)++] = new_range;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 FixVDisaDiffIvfcHashChain(bool partitionB) {
|
||||
VDisaDiffPartitionInfo* info = partitionB ? partitionB_info : partitionA_info;
|
||||
if (!info) return 1;
|
||||
|
||||
|
||||
if (info->n_ivfc_ranges == 0)
|
||||
return 0;
|
||||
|
||||
|
||||
DisaDiffIvfcRange* ivfc_range_buffer = malloc(sizeof(DisaDiffIvfcRange) * MAX_IVFC_RANGES);
|
||||
if (!ivfc_range_buffer)
|
||||
return 1;
|
||||
|
||||
|
||||
u32 n_ivfc_ranges = 0;
|
||||
|
||||
|
||||
for (u32 i = 0; i < info->n_ivfc_ranges; i++) {
|
||||
if (MergeDisaDiffIvfcRange(info->ivfc_lvl4_ranges[i], ivfc_range_buffer, &n_ivfc_ranges) != 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
for (u32 level = 4; level > 0; level--) {
|
||||
u32 next_n_ivfc_ranges = 0;
|
||||
DisaDiffIvfcRange* ivfc_ranges = (level % 2) ? info->ivfc_lvl4_ranges : ivfc_range_buffer;
|
||||
DisaDiffIvfcRange* next_ivfc_ranges = (level % 2) ? ivfc_range_buffer : info->ivfc_lvl4_ranges;
|
||||
|
||||
|
||||
for (u32 j = 0; j < n_ivfc_ranges; j++) {
|
||||
DisaDiffIvfcRange next_range;
|
||||
|
||||
|
||||
if (FixDisaDiffIvfcLevel(&(info->rw_info), level, ivfc_ranges[j].offset, ivfc_ranges[j].size, &(next_range.offset), &(next_range.size)) != 0)
|
||||
return 1;
|
||||
|
||||
|
||||
if (next_ivfc_ranges) {
|
||||
AlignDisaDiffIvfcRange(&next_range, (&(info->rw_info.log_ivfc_lvl1))[level - 2]);
|
||||
if (MergeDisaDiffIvfcRange(next_range, next_ivfc_ranges, &next_n_ivfc_ranges) != 0)
|
||||
@ -119,12 +119,12 @@ static u32 FixVDisaDiffIvfcHashChain(bool partitionB) {
|
||||
}
|
||||
n_ivfc_ranges = next_n_ivfc_ranges;
|
||||
}
|
||||
|
||||
|
||||
free(ivfc_range_buffer);
|
||||
|
||||
|
||||
if (FixDisaDiffIvfcLevel(&(info->rw_info), 0, 0, 0, NULL, NULL) != 0)
|
||||
return 1;
|
||||
|
||||
|
||||
info->n_ivfc_ranges = 0;
|
||||
return 0;
|
||||
}
|
||||
@ -137,7 +137,7 @@ void DeinitVDisaDiffDrive(void) {
|
||||
free(partitionA_info);
|
||||
partitionA_info = NULL;
|
||||
}
|
||||
|
||||
|
||||
if (partitionB_info) {
|
||||
FixVDisaDiffIvfcHashChain(true);
|
||||
if (partitionB_info->rw_info.dpfs_lvl2_cache)
|
||||
@ -150,28 +150,28 @@ void DeinitVDisaDiffDrive(void) {
|
||||
u64 InitVDisaDiffDrive(void) {
|
||||
DisaDiffRWInfo info;
|
||||
u64 type;
|
||||
|
||||
|
||||
DeinitVDisaDiffDrive();
|
||||
|
||||
|
||||
if (!(type = (GetMountState() & (SYS_DISA | SYS_DIFF))))
|
||||
return 0;
|
||||
|
||||
|
||||
if ((GetDisaDiffRWInfo(NULL, &info, false) != 0) ||
|
||||
(!(info.dpfs_lvl2_cache = (u8*) malloc(info.size_dpfs_lvl2)) ||
|
||||
(BuildDisaDiffDpfsLvl2Cache(NULL, &info, info.dpfs_lvl2_cache, info.size_dpfs_lvl2) != 0))) {
|
||||
free(info.dpfs_lvl2_cache);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if (!(partitionA_info = malloc(sizeof(VDisaDiffPartitionInfo)))) {
|
||||
free(info.dpfs_lvl2_cache);
|
||||
partitionA_info = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
memset(partitionA_info, 0, sizeof(VDisaDiffPartitionInfo));
|
||||
partitionA_info->rw_info = info;
|
||||
|
||||
|
||||
if ((type & SYS_DISA) && (GetDisaDiffRWInfo(NULL, &info, true) == 0)) {
|
||||
if (!(info.dpfs_lvl2_cache = (u8*) malloc(info.size_dpfs_lvl2)) ||
|
||||
(BuildDisaDiffDpfsLvl2Cache(NULL, &info, info.dpfs_lvl2_cache, info.size_dpfs_lvl2) != 0)) {
|
||||
@ -180,7 +180,7 @@ u64 InitVDisaDiffDrive(void) {
|
||||
partitionA_info = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if (!(partitionB_info = malloc(sizeof(VDisaDiffPartitionInfo)))) {
|
||||
if (info.dpfs_lvl2_cache) free(info.dpfs_lvl2_cache);
|
||||
if (partitionA_info->rw_info.dpfs_lvl2_cache) free(partitionA_info->rw_info.dpfs_lvl2_cache);
|
||||
@ -189,19 +189,19 @@ u64 InitVDisaDiffDrive(void) {
|
||||
partitionB_info = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
memset(partitionB_info, 0, sizeof(VDisaDiffPartitionInfo));
|
||||
partitionB_info->rw_info = info;
|
||||
}
|
||||
|
||||
|
||||
InitVBDRIDrive();
|
||||
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
u64 CheckVDisaDiffDrive(void) {
|
||||
u64 type = GetMountState();
|
||||
|
||||
|
||||
return ((type & (SYS_DISA | SYS_DIFF)) && partitionA_info) ? type : 0;
|
||||
}
|
||||
|
||||
@ -209,17 +209,17 @@ u64 CheckVDisaDiffDrive(void) {
|
||||
bool ReadVDisaDiffDir(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
if (++(vdir->index) > 1)
|
||||
return false;
|
||||
|
||||
|
||||
VDisaDiffPartitionInfo* info = vdir->index ? partitionB_info : partitionA_info;
|
||||
|
||||
|
||||
if (!info)
|
||||
return false;
|
||||
|
||||
|
||||
memset(vfile, 0, sizeof(VirtualFile));
|
||||
snprintf(vfile->name, 32, "partition%c.bin", vdir->index ? 'B' : 'A');
|
||||
vfile->size = info->rw_info.size_ivfc_lvl4;
|
||||
if (vdir->index) vfile->flags |= VFLAG_PARTITION_B;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -234,9 +234,9 @@ int ReadVDisaDiffFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 co
|
||||
int WriteVDisaDiffFile(const VirtualFile* vfile, const void* buffer, u64 offset, u64 count) {
|
||||
VDisaDiffPartitionInfo* info = (vfile->flags & VFLAG_PARTITION_B) ? partitionB_info : partitionA_info;
|
||||
if (!info) return 1;
|
||||
|
||||
|
||||
if (WriteDisaDiffIvfcLvl4(NULL, &(info->rw_info), offset, count, buffer) != count) return 1;
|
||||
|
||||
|
||||
DisaDiffIvfcRange range;
|
||||
range.offset = offset;
|
||||
range.size = count;
|
||||
@ -245,6 +245,6 @@ int WriteVDisaDiffFile(const VirtualFile* vfile, const void* buffer, u64 offset,
|
||||
((FixVDisaDiffIvfcHashChain(vfile->flags & VFLAG_PARTITION_B) != 0) ||
|
||||
(MergeDisaDiffIvfcRange(range, info->ivfc_lvl4_ranges, &(info->n_ivfc_ranges)) != 0)))
|
||||
return 1;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ int ReadCbcImageBlocks(void* buffer, u64 block, u64 count, u8* iv0, u64 block0)
|
||||
if (block == block0) memcpy(ctr, iv0, AES_BLOCK_SIZE);
|
||||
else if ((ret = ReadImageBytes(ctr, (block-1) * AES_BLOCK_SIZE, AES_BLOCK_SIZE)) != 0)
|
||||
return ret;
|
||||
|
||||
|
||||
u32 mode = AES_CNT_TITLEKEY_DECRYPT_MODE;
|
||||
cbc_decrypt(buffer, buffer, count, mode, ctr);
|
||||
}
|
||||
@ -139,7 +139,7 @@ int ReadCbcImageBytes(void* buffer, u64 offset, u64 count, u8* iv0, u64 offset0)
|
||||
u8 __attribute__((aligned(32))) temp[AES_BLOCK_SIZE];
|
||||
u8* buffer8 = (u8*) buffer;
|
||||
int ret = 0;
|
||||
|
||||
|
||||
if (off_fix) { // misaligned offset (at beginning)
|
||||
u32 fix_byte = ((off_fix + count) >= AES_BLOCK_SIZE) ? AES_BLOCK_SIZE - off_fix : count;
|
||||
if ((ret = ReadCbcImageBlocks(temp, offset / AES_BLOCK_SIZE, 1, iv0, block0)) != 0)
|
||||
@ -149,7 +149,7 @@ int ReadCbcImageBytes(void* buffer, u64 offset, u64 count, u8* iv0, u64 offset0)
|
||||
offset += fix_byte;
|
||||
count -= fix_byte;
|
||||
}
|
||||
|
||||
|
||||
if (count >= AES_BLOCK_SIZE) {
|
||||
u64 blocks = count / AES_BLOCK_SIZE;
|
||||
if ((ret = ReadCbcImageBlocks(buffer8, offset / AES_BLOCK_SIZE, blocks, iv0, block0)) != 0)
|
||||
@ -158,14 +158,14 @@ int ReadCbcImageBytes(void* buffer, u64 offset, u64 count, u8* iv0, u64 offset0)
|
||||
offset += AES_BLOCK_SIZE * blocks;
|
||||
count -= AES_BLOCK_SIZE * blocks;
|
||||
}
|
||||
|
||||
|
||||
if (count) { // misaligned offset (at end)
|
||||
if ((ret = ReadCbcImageBlocks(temp, offset / AES_BLOCK_SIZE, 1, iv0, block0)) != 0)
|
||||
return ret;
|
||||
memcpy(buffer8, temp, count);
|
||||
count = 0;
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -175,12 +175,12 @@ int ReadCiaContentImageBytes(void* buffer, u64 offset, u64 count, u32 cia_cnt_id
|
||||
memcpy(tik, cia_titlekey, 16);
|
||||
setup_aeskey(0x11, tik);
|
||||
use_aeskey(0x11);
|
||||
|
||||
|
||||
// setup IV0
|
||||
u8 iv0[AES_BLOCK_SIZE] = { 0 };
|
||||
iv0[0] = (cia_cnt_idx >> 8) & 0xFF;
|
||||
iv0[1] = (cia_cnt_idx >> 0) & 0xFF;
|
||||
|
||||
|
||||
// continue in next function
|
||||
u8* iv0_ptr = (cia_cnt_idx <= 0xFFFF) ? iv0 : NULL;
|
||||
return ReadCbcImageBytes(buffer, offset, count, iv0_ptr, offset0);
|
||||
@ -205,7 +205,7 @@ int ReadNcchImageBytes(void* buffer, u64 offset, u64 count) {
|
||||
bool BuildVGameExeFsDir(void) {
|
||||
VirtualFile* templates = templates_exefs;
|
||||
u32 n = 0;
|
||||
|
||||
|
||||
for (u32 i = 0; i < 10; i++) {
|
||||
ExeFsFileHeader* file = exefs->files + i;
|
||||
if (file->size == 0) continue;
|
||||
@ -217,7 +217,7 @@ bool BuildVGameExeFsDir(void) {
|
||||
templates[n].flags = VFLAG_EXEFS_FILE;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
n_templates_exefs = n;
|
||||
return true;
|
||||
}
|
||||
@ -225,10 +225,10 @@ bool BuildVGameExeFsDir(void) {
|
||||
bool BuildVGameNcchDir(void) {
|
||||
VirtualFile* templates = templates_ncch;
|
||||
u32 n = 0;
|
||||
|
||||
|
||||
// NCCH crypto
|
||||
bool ncch_crypto = (NCCH_ENCRYPTED(ncch)) && (SetupNcchCrypto(ncch, NCCH_NOCRYPTO) == 0);
|
||||
|
||||
|
||||
// header
|
||||
strncpy(templates[n].name, NAME_NCCH_HEADER, 32);
|
||||
templates[n].offset = offset_ncch + 0;
|
||||
@ -236,7 +236,7 @@ bool BuildVGameNcchDir(void) {
|
||||
templates[n].keyslot = 0xFF;
|
||||
templates[n].flags = VFLAG_NCCH;
|
||||
n++;
|
||||
|
||||
|
||||
// extended header
|
||||
if (ncch->size_exthdr) {
|
||||
strncpy(templates[n].name, NAME_NCCH_EXTHEADER, 32);
|
||||
@ -246,7 +246,7 @@ bool BuildVGameNcchDir(void) {
|
||||
templates[n].flags = VFLAG_EXTHDR;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// plain region
|
||||
if (ncch->size_plain) {
|
||||
strncpy(templates[n].name, NAME_NCCH_PLAIN, 32);
|
||||
@ -256,7 +256,7 @@ bool BuildVGameNcchDir(void) {
|
||||
templates[n].flags = VFLAG_NCCH;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// logo region
|
||||
if (ncch->size_logo) {
|
||||
strncpy(templates[n].name, NAME_NCCH_LOGO, 32);
|
||||
@ -266,7 +266,7 @@ bool BuildVGameNcchDir(void) {
|
||||
templates[n].flags = VFLAG_NCCH;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// exefs
|
||||
if (ncch->size_exefs) {
|
||||
strncpy(templates[n].name, NAME_NCCH_EXEFS, 32);
|
||||
@ -282,7 +282,7 @@ bool BuildVGameNcchDir(void) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// romfs
|
||||
if (ncch->size_romfs) {
|
||||
strncpy(templates[n].name, NAME_NCCH_ROMFS, 32);
|
||||
@ -298,16 +298,16 @@ bool BuildVGameNcchDir(void) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
n_templates_ncch = n;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool BuildVGameNcsdDir(void) {
|
||||
const char* name_type[] = { NAME_NCSD_TYPES };
|
||||
VirtualFile* templates = templates_ncsd;
|
||||
u32 n = 0;
|
||||
|
||||
|
||||
// header
|
||||
strncpy(templates[n].name, NAME_NCSD_HEADER, 32);
|
||||
templates[n].offset = 0;
|
||||
@ -315,7 +315,7 @@ bool BuildVGameNcsdDir(void) {
|
||||
templates[n].keyslot = 0xFF;
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
|
||||
|
||||
// card info header
|
||||
if (ncsd->partitions[0].offset * NCSD_MEDIA_UNIT >= NCSD_CINFO_OFFSET + NCSD_CINFO_SIZE) {
|
||||
strncpy(templates[n].name, NAME_NCSD_CARDINFO, 32);
|
||||
@ -325,7 +325,7 @@ bool BuildVGameNcsdDir(void) {
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// dev info header
|
||||
if (ncsd->partitions[0].offset * NCSD_MEDIA_UNIT >= NCSD_DINFO_OFFSET + NCSD_DINFO_SIZE) {
|
||||
strncpy(templates[n].name, NAME_NCSD_DEVINFO, 32);
|
||||
@ -335,7 +335,7 @@ bool BuildVGameNcsdDir(void) {
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// contents
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
NcchPartition* partition = ncsd->partitions + i;
|
||||
@ -352,7 +352,7 @@ bool BuildVGameNcsdDir(void) {
|
||||
templates[n].flags |= (VFLAG_NCCH | VFLAG_DIR);
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
n_templates_ncsd = n;
|
||||
return true;
|
||||
}
|
||||
@ -361,10 +361,10 @@ bool BuildVGameCiaDir(void) {
|
||||
CiaInfo info;
|
||||
VirtualFile* templates = templates_cia;
|
||||
u32 n = 0;
|
||||
|
||||
|
||||
if (GetCiaInfo(&info, &(cia->header)) != 0)
|
||||
return false;
|
||||
|
||||
|
||||
// header
|
||||
strncpy(templates[n].name, NAME_CIA_HEADER, 32);
|
||||
templates[n].offset = 0;
|
||||
@ -372,7 +372,7 @@ bool BuildVGameCiaDir(void) {
|
||||
templates[n].keyslot = 0xFF;
|
||||
templates[n].flags = VFLAG_NO_CRYPTO;
|
||||
n++;
|
||||
|
||||
|
||||
// certificates
|
||||
if (info.size_cert) {
|
||||
strncpy(templates[n].name, NAME_CIA_CERT, 32);
|
||||
@ -382,7 +382,7 @@ bool BuildVGameCiaDir(void) {
|
||||
templates[n].flags = VFLAG_NO_CRYPTO;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// ticket
|
||||
if (info.size_ticket) {
|
||||
strncpy(templates[n].name, NAME_CIA_TICKET, 32);
|
||||
@ -392,7 +392,7 @@ bool BuildVGameCiaDir(void) {
|
||||
templates[n].flags = VFLAG_NO_CRYPTO;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// TMD (the full thing)
|
||||
if (info.size_tmd) {
|
||||
strncpy(templates[n].name, NAME_CIA_TMD, 32);
|
||||
@ -402,7 +402,7 @@ bool BuildVGameCiaDir(void) {
|
||||
templates[n].flags = VFLAG_NO_CRYPTO;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// TMD content chunks
|
||||
if (info.size_content_list) {
|
||||
strncpy(templates[n].name, NAME_CIA_TMDCHUNK, 32);
|
||||
@ -412,7 +412,7 @@ bool BuildVGameCiaDir(void) {
|
||||
templates[n].flags = VFLAG_NO_CRYPTO;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// meta
|
||||
if (info.size_meta) {
|
||||
strncpy(templates[n].name, NAME_CIA_META, 32);
|
||||
@ -428,7 +428,7 @@ bool BuildVGameCiaDir(void) {
|
||||
templates[n].flags = VFLAG_NO_CRYPTO;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// contents
|
||||
if (info.size_content) {
|
||||
TmdContentChunk* content_list = cia->content_list;
|
||||
@ -443,7 +443,7 @@ bool BuildVGameCiaDir(void) {
|
||||
|
||||
if (!(cnt_index[index/8] & (1 << (7-(index%8)))))
|
||||
continue; // skip missing contents
|
||||
|
||||
|
||||
u32 cnt_type = 0;
|
||||
if (size >= 0x200) {
|
||||
u8 header[0x200];
|
||||
@ -451,25 +451,25 @@ bool BuildVGameCiaDir(void) {
|
||||
cnt_type = (ValidateNcchHeader((NcchHeader*)(void*)header) == 0) ? VFLAG_NCCH :
|
||||
(ValidateTwlHeader((TwlHeader*)(void*)header) == 0) ? VFLAG_NDS : 0;
|
||||
}
|
||||
|
||||
|
||||
snprintf(templates[n].name, 32, NAME_CIA_CONTENT, index, id, ".app");
|
||||
templates[n].offset = next_offset;
|
||||
templates[n].size = size;
|
||||
templates[n].keyslot = keyslot; // keyslot is used for CIA content index here
|
||||
templates[n].flags = VFLAG_CIA_CONTENT;
|
||||
n++;
|
||||
|
||||
|
||||
if (cnt_type) {
|
||||
memcpy(templates + n, templates + n - 1, sizeof(VirtualFile));
|
||||
snprintf(templates[n].name, 32, NAME_CIA_CONTENT, index, id, "");
|
||||
templates[n].flags |= (cnt_type | VFLAG_DIR);
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
next_offset += size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
n_templates_cia = n;
|
||||
return true;
|
||||
}
|
||||
@ -477,7 +477,7 @@ bool BuildVGameCiaDir(void) {
|
||||
bool BuildVGameNdsDir(void) {
|
||||
VirtualFile* templates = templates_nds;
|
||||
u32 n = 0;
|
||||
|
||||
|
||||
// header
|
||||
strncpy(templates[n].name, NAME_NDS_HEADER, 32);
|
||||
templates[n].offset = offset_nds + 0;
|
||||
@ -485,7 +485,7 @@ bool BuildVGameNdsDir(void) {
|
||||
templates[n].keyslot = 0xFF;
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
|
||||
|
||||
// banner
|
||||
if (twl->icon_offset) {
|
||||
u16 v = 0;
|
||||
@ -497,7 +497,7 @@ bool BuildVGameNdsDir(void) {
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// ARM9 section (+ ARM9 section footer)
|
||||
if (twl->arm9_size) {
|
||||
u32 f = 0;
|
||||
@ -510,7 +510,7 @@ bool BuildVGameNdsDir(void) {
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// ARM9 overlay section
|
||||
if (twl->arm9_overlay_size) {
|
||||
strncpy(templates[n].name, NAME_NDS_ARM9OVL, 32);
|
||||
@ -520,7 +520,7 @@ bool BuildVGameNdsDir(void) {
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// ARM9i section
|
||||
if ((twl->unit_code != TWL_UNITCODE_NTR) && (twl->arm9i_size)) {
|
||||
strncpy(templates[n].name, NAME_NDS_ARM9I, 32);
|
||||
@ -530,7 +530,7 @@ bool BuildVGameNdsDir(void) {
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// ARM7 section
|
||||
if (twl->arm7_size) {
|
||||
strncpy(templates[n].name, NAME_NDS_ARM7, 32);
|
||||
@ -540,7 +540,7 @@ bool BuildVGameNdsDir(void) {
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// ARM7 overlay section
|
||||
if (twl->arm7_overlay_size) {
|
||||
strncpy(templates[n].name, NAME_NDS_ARM7OVL, 32);
|
||||
@ -550,7 +550,7 @@ bool BuildVGameNdsDir(void) {
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// ARM7i section
|
||||
if ((twl->unit_code != TWL_UNITCODE_NTR) && (twl->arm7i_size)) {
|
||||
strncpy(templates[n].name, NAME_NDS_ARM7I, 32);
|
||||
@ -560,7 +560,7 @@ bool BuildVGameNdsDir(void) {
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// data
|
||||
if (twl->fnt_size && twl->fat_size && (twl->fnt_offset < twl->fat_offset)) {
|
||||
strncpy(templates[n].name, NAME_NDS_DATADIR, 32);
|
||||
@ -570,7 +570,7 @@ bool BuildVGameNdsDir(void) {
|
||||
templates[n].flags = VFLAG_NITRO_DIR | VFLAG_DIR;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
n_templates_nds = n;
|
||||
return true;
|
||||
}
|
||||
@ -578,7 +578,7 @@ bool BuildVGameNdsDir(void) {
|
||||
bool BuildVGameFirmDir(void) {
|
||||
VirtualFile* templates = templates_firm;
|
||||
u32 n = 0;
|
||||
|
||||
|
||||
// header
|
||||
strncpy(templates[n].name, NAME_FIRM_HEADER, 32);
|
||||
templates[n].offset = 0;
|
||||
@ -586,7 +586,7 @@ bool BuildVGameFirmDir(void) {
|
||||
templates[n].keyslot = 0xFF;
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
|
||||
|
||||
// arm9 binary (only for encrypted)
|
||||
if (offset_a9bin != (u64) -1) {
|
||||
strncpy(templates[n].name, NAME_FIRM_ARM9BIN, 32);
|
||||
@ -596,7 +596,7 @@ bool BuildVGameFirmDir(void) {
|
||||
templates[n].flags = 0;
|
||||
n++;
|
||||
}
|
||||
|
||||
|
||||
// section binaries & NCCHs
|
||||
for (u32 i = 0; i < 4; i++) {
|
||||
FirmSectionHeader* section = firm->sections + i;
|
||||
@ -614,7 +614,7 @@ bool BuildVGameFirmDir(void) {
|
||||
NcchHeader p9_ncch;
|
||||
char name[8];
|
||||
u32 offset_p9 = 0;
|
||||
|
||||
|
||||
u8* buffer = (u8*) malloc(section->size);
|
||||
if (buffer) {
|
||||
if (ReadGameImageBytes(buffer, section->offset, section->size) != 0) break;
|
||||
@ -627,7 +627,7 @@ bool BuildVGameFirmDir(void) {
|
||||
}
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
|
||||
if (offset_p9) {
|
||||
snprintf(templates[n].name, 32, NAME_FIRM_NCCH, p9_ncch.programId, name, ".app");
|
||||
templates[n].offset = offset_p9;
|
||||
@ -651,7 +651,7 @@ bool BuildVGameFirmDir(void) {
|
||||
(ReadImageBytes((u8*) name, section->offset + p + 0x200, 0x8) != 0) ||
|
||||
(ValidateNcchHeader(&firm_ncch) != 0))
|
||||
break;
|
||||
|
||||
|
||||
snprintf(templates[n].name, 32, NAME_FIRM_NCCH, firm_ncch.programId, name, ".app");
|
||||
templates[n].offset = section->offset + p;
|
||||
templates[n].size = firm_ncch.size * NCCH_MEDIA_UNIT;
|
||||
@ -666,7 +666,7 @@ bool BuildVGameFirmDir(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
n_templates_firm = n;
|
||||
return true;
|
||||
}
|
||||
@ -676,7 +676,7 @@ bool BuildVGameTadDir(void) {
|
||||
VirtualFile* templates = templates_tad;
|
||||
u32 content_offset = 0;
|
||||
u32 n = 0;
|
||||
|
||||
|
||||
// read header, setup table
|
||||
u8 ALIGN(32) hdr_data[TAD_HEADER_LEN];
|
||||
TadHeader* hdr = (void*)hdr_data;
|
||||
@ -686,7 +686,7 @@ bool BuildVGameTadDir(void) {
|
||||
n_templates_tad = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// banner
|
||||
strncpy(templates[n].name, NAME_TAD_BANNER, 32);
|
||||
templates[n].offset = content_offset;
|
||||
@ -695,7 +695,7 @@ bool BuildVGameTadDir(void) {
|
||||
templates[n].flags = 0;
|
||||
content_offset = tbl.banner_end;
|
||||
n++;
|
||||
|
||||
|
||||
// header
|
||||
strncpy(templates[n].name, NAME_TAD_HEADER, 32);
|
||||
templates[n].offset = content_offset;
|
||||
@ -704,7 +704,7 @@ bool BuildVGameTadDir(void) {
|
||||
templates[n].flags = 0;
|
||||
content_offset = tbl.header_end;
|
||||
n++;
|
||||
|
||||
|
||||
// footer
|
||||
strncpy(templates[n].name, NAME_TAD_FOOTER, 32);
|
||||
templates[n].offset = content_offset;
|
||||
@ -713,7 +713,7 @@ bool BuildVGameTadDir(void) {
|
||||
templates[n].flags = 0;
|
||||
content_offset = tbl.footer_end;
|
||||
n++;
|
||||
|
||||
|
||||
// contents
|
||||
for (u32 i = 0; i < TAD_NUM_CONTENT; content_offset = tbl.content_end[i++]) {
|
||||
if (!hdr->content_size[i]) continue; // nothing in section
|
||||
@ -731,7 +731,7 @@ bool BuildVGameTadDir(void) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
n_templates_tad = n;
|
||||
return true;
|
||||
}
|
||||
@ -745,10 +745,10 @@ void DeinitVGameDrive(void) {
|
||||
|
||||
u64 InitVGameDrive(void) { // prerequisite: game file mounted as image
|
||||
u64 type = GetMountState();
|
||||
|
||||
|
||||
vgame_type = 0;
|
||||
DeinitVGameDrive();
|
||||
|
||||
|
||||
offset_firm = (u64) -1;
|
||||
offset_a9bin = (u64) -1;
|
||||
offset_cia = (u64) -1;
|
||||
@ -761,7 +761,7 @@ u64 InitVGameDrive(void) { // prerequisite: game file mounted as image
|
||||
offset_nds = (u64) -1;
|
||||
offset_nitro = (u64) -1;
|
||||
offset_tad = (u64) -1;
|
||||
|
||||
|
||||
base_vdir =
|
||||
(type & SYS_FIRM ) ? VFLAG_FIRM :
|
||||
(type & GAME_CIA ) ? VFLAG_CIA :
|
||||
@ -792,7 +792,7 @@ u64 InitVGameDrive(void) { // prerequisite: game file mounted as image
|
||||
ncch = (NcchHeader*) (void*) (((u8*) vgame_buffer) + 0x3FC00); // 512 byte reserved
|
||||
exefs = (ExeFsHeader*) (void*) (((u8*) vgame_buffer) + 0x3FE00); // 512 byte reserved
|
||||
// filesystem stuff (RomFS / NitroFS) will be allocated on demand
|
||||
|
||||
|
||||
vgame_type = type;
|
||||
return type;
|
||||
}
|
||||
@ -816,7 +816,7 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) {
|
||||
vdir->size = ventry->size;
|
||||
vdir->flags = ventry->flags;
|
||||
}
|
||||
|
||||
|
||||
// CIA content special handling
|
||||
if (vdir->flags & VFLAG_CIA) { // disable content crypto
|
||||
offset_ccnt = (u64) -1;
|
||||
@ -825,7 +825,7 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) {
|
||||
offset_ccnt = vdir->offset;
|
||||
index_ccnt = ventry->keyslot;
|
||||
}
|
||||
|
||||
|
||||
// build directories where required
|
||||
if ((vdir->flags & VFLAG_FIRM) && (offset_firm != vdir->offset)) {
|
||||
if ((ReadImageBytes((u8*) firm, 0, sizeof(FirmHeader)) != 0) ||
|
||||
@ -921,8 +921,8 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) {
|
||||
return false;
|
||||
offset_nitro = offset_nds;
|
||||
}
|
||||
|
||||
// for romfs/nitro dir: switch to lv3/nitro object
|
||||
|
||||
// for romfs/nitro dir: switch to lv3/nitro object
|
||||
if (vdir->flags & (VFLAG_ROMFS|VFLAG_NITRO_DIR)) {
|
||||
vdir->index = -1;
|
||||
vdir->offset = 0;
|
||||
@ -931,19 +931,19 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) {
|
||||
if (vdir->flags & VFLAG_NITRO_DIR) vdir->flags |= VFLAG_NITRO;
|
||||
vdir->flags &= ~(VFLAG_ROMFS|VFLAG_NITRO_DIR);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadVGameDirLv3(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
vfile->name[0] = '\0';
|
||||
vfile->flags = VFLAG_LV3 | VFLAG_READONLY;
|
||||
vfile->keyslot = ((offset_ncch != (u64) -1) && NCCH_ENCRYPTED(ncch)) ?
|
||||
vfile->keyslot = ((offset_ncch != (u64) -1) && NCCH_ENCRYPTED(ncch)) ?
|
||||
0x2C : 0xFF; // actual keyslot may be different
|
||||
|
||||
|
||||
// start from parent dir object
|
||||
if (vdir->index == -1) vdir->index = 0;
|
||||
|
||||
if (vdir->index == -1) vdir->index = 0;
|
||||
|
||||
// first child file object, skip if not available
|
||||
if (vdir->index == 0) {
|
||||
RomFsLv3DirMeta* parent = LV3_GET_DIR(vdir->offset, &lv3idx);
|
||||
@ -958,7 +958,7 @@ bool ReadVGameDirLv3(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
return true;
|
||||
} else vdir->index = 2;
|
||||
}
|
||||
|
||||
|
||||
// parse sibling files
|
||||
if (vdir->index == 1) {
|
||||
RomFsLv3FileMeta* current = LV3_GET_FILE(vdir->offset, &lv3idx);
|
||||
@ -975,7 +975,7 @@ bool ReadVGameDirLv3(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
vdir->index = 2;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
|
||||
// first child dir object, skip if not available
|
||||
if (vdir->index == 2) {
|
||||
RomFsLv3DirMeta* parent = LV3_GET_DIR(vdir->offset, &lv3idx);
|
||||
@ -988,7 +988,7 @@ bool ReadVGameDirLv3(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
return true;
|
||||
} else vdir->index = 4;
|
||||
}
|
||||
|
||||
|
||||
// parse sibling dirs
|
||||
if (vdir->index == 3) {
|
||||
RomFsLv3DirMeta* current = LV3_GET_DIR(vdir->offset, &lv3idx);
|
||||
@ -1003,18 +1003,18 @@ bool ReadVGameDirLv3(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
vdir->index = 4;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadVGameDirNitro(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
u8* fnt = vgame_fs_buffer;
|
||||
u8* fat = vgame_fs_buffer + twl->fat_offset - twl->fnt_offset;
|
||||
|
||||
|
||||
vfile->name[0] = '\0';
|
||||
vfile->flags = VFLAG_NITRO | VFLAG_READONLY;
|
||||
vfile->keyslot = 0;
|
||||
|
||||
|
||||
// start from parent dir object
|
||||
if (vdir->index == -1) {
|
||||
u8* fnt_entry = NULL;
|
||||
@ -1025,7 +1025,7 @@ bool ReadVGameDirNitro(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
vdir->offset = (vdir->offset&0xFFFFFFFF) | (((u64)(fnt_entry - fnt)) << 32); // store offsets in offset
|
||||
} else vdir->index = -3; // error
|
||||
}
|
||||
|
||||
|
||||
// read directory entries until done
|
||||
if (vdir->index >= 0) {
|
||||
u8* fnt_entry = fnt + (vdir->offset >> 32);
|
||||
@ -1041,14 +1041,14 @@ bool ReadVGameDirNitro(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
vdir->offset = (vdir->offset&0xFFFFFFFF) | (((u64)(fnt_entry - fnt)) << 32);
|
||||
} else vdir->index = -2; // end of dir
|
||||
}
|
||||
|
||||
|
||||
return (vdir->index >= 0);
|
||||
}
|
||||
|
||||
|
||||
bool ReadVGameDir(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
VirtualFile* templates = NULL;
|
||||
int n = 0;
|
||||
|
||||
|
||||
if (vdir->flags & VFLAG_FIRM) {
|
||||
templates = templates_firm;
|
||||
n = n_templates_firm;
|
||||
@ -1075,14 +1075,14 @@ bool ReadVGameDir(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
} else if (vdir->flags & VFLAG_NITRO) {
|
||||
return ReadVGameDirNitro(vfile, vdir);
|
||||
}
|
||||
|
||||
|
||||
if (++vdir->index < n) {
|
||||
// copy current template to vfile and set readonly flag
|
||||
memcpy(vfile, templates + vdir->index, sizeof(VirtualFile));
|
||||
vfile->flags |= VFLAG_READONLY;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1110,7 +1110,7 @@ bool FindVirtualFileInLv3Dir(VirtualFile* vfile, const VirtualDir* vdir, const c
|
||||
vfile->flags = vdir->flags & ~VFLAG_DIR;
|
||||
vfile->keyslot = ((offset_ncch != (u64) -1) && NCCH_ENCRYPTED(ncch)) ?
|
||||
0x2C : 0xFF; // actual keyslot may be different
|
||||
|
||||
|
||||
RomFsLv3DirMeta* lv3dir = GetLv3DirMeta(name, vdir->offset, &lv3idx);
|
||||
if (lv3dir) {
|
||||
vfile->offset = ((u8*) lv3dir) - ((u8*) lv3idx.dirmeta);
|
||||
@ -1118,24 +1118,24 @@ bool FindVirtualFileInLv3Dir(VirtualFile* vfile, const VirtualDir* vdir, const c
|
||||
vfile->flags |= VFLAG_DIR;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
RomFsLv3FileMeta* lv3file = GetLv3FileMeta(name, vdir->offset, &lv3idx);
|
||||
if (lv3file) {
|
||||
vfile->offset = ((u8*) lv3file) - ((u8*) lv3idx.filemeta);
|
||||
vfile->size = lv3file->size_data;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetVGameLv3Filename(char* name, const VirtualFile* vfile, u32 n_chars) {
|
||||
if (!(vfile->flags & VFLAG_LV3))
|
||||
return false;
|
||||
|
||||
|
||||
u16* wname = NULL;
|
||||
u32 name_len = 0;
|
||||
|
||||
|
||||
if (vfile->flags & VFLAG_DIR) {
|
||||
RomFsLv3DirMeta* dirmeta = LV3_GET_DIR(vfile->offset, &lv3idx);
|
||||
if (!dirmeta) return false;
|
||||
@ -1149,14 +1149,14 @@ bool GetVGameLv3Filename(char* name, const VirtualFile* vfile, u32 n_chars) {
|
||||
}
|
||||
memset(name, 0, n_chars);
|
||||
utf16_to_utf8((u8*) name, wname, n_chars-1, name_len);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetVGameNitroFilename(char* name, const VirtualFile* vfile, u32 n_chars) {
|
||||
if (!(vfile->flags & VFLAG_NITRO))
|
||||
return false;
|
||||
|
||||
|
||||
u8* fnt_entry = vgame_fs_buffer + (vfile->offset >> 32);
|
||||
u32 name_len = (*fnt_entry) & ~0x80;
|
||||
if (name_len >= n_chars) return false;
|
||||
@ -1164,7 +1164,7 @@ bool GetVGameNitroFilename(char* name, const VirtualFile* vfile, u32 n_chars) {
|
||||
memcpy(name, fnt_entry + 1, name_len);
|
||||
for (u32 i = 0; i < name_len; i++)
|
||||
if (name[i] == '%') name[i] = '_';
|
||||
|
||||
|
||||
// Shift-JIS workaround
|
||||
for (u32 i = 0; i < name_len; i++) {
|
||||
if (name[i] >= 0x80) { // this is a Shift-JIS filename
|
||||
@ -1173,7 +1173,7 @@ bool GetVGameNitroFilename(char* name, const VirtualFile* vfile, u32 n_chars) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -112,17 +112,17 @@ bool GetVirtualFile(VirtualFile* vfile, const char* path, u8 mode) {
|
||||
char lpath[256];
|
||||
strncpy(lpath, path, 256);
|
||||
lpath[255] = '\0';
|
||||
|
||||
|
||||
// get virtual source / root dir object
|
||||
u32 virtual_src = 0;
|
||||
virtual_src = GetVirtualSource(path);
|
||||
if (!virtual_src) return false;
|
||||
|
||||
|
||||
// set vfile as root object
|
||||
memset(vfile, 0, sizeof(VirtualDir));
|
||||
vfile->flags = VFLAG_ROOT|virtual_src;
|
||||
if (strnlen(lpath, 256) <= 3) return true;
|
||||
|
||||
|
||||
// tokenize / parse path
|
||||
char* name;
|
||||
VirtualDir vdir;
|
||||
@ -130,7 +130,7 @@ bool GetVirtualFile(VirtualFile* vfile, const char* path, u8 mode) {
|
||||
for (name = strtok(lpath + 3, "/"); name && vdir.flags; name = strtok(NULL, "/")) {
|
||||
if (!(vdir.flags & VFLAG_LV3)) { // standard method
|
||||
while (true) {
|
||||
if (!ReadVirtualDir(vfile, &vdir))
|
||||
if (!ReadVirtualDir(vfile, &vdir))
|
||||
return ((mode & FA_WRITE) && (vdir.flags & VRT_BDRI) && GetNewVBDRIFile(vfile, &vdir, path));
|
||||
if ((!(vfile->flags & (VRT_GAME|VRT_VRAM)) && (strncasecmp(name, vfile->name, 32) == 0)) ||
|
||||
((vfile->flags & VRT_GAME) && MatchVGameFilename(name, vfile, 256)) ||
|
||||
@ -144,7 +144,7 @@ bool GetVirtualFile(VirtualFile* vfile, const char* path, u8 mode) {
|
||||
if (!OpenVirtualDir(&vdir, vfile))
|
||||
vdir.flags = 0;
|
||||
}
|
||||
|
||||
|
||||
return (name == NULL); // if name is NULL, this succeeded
|
||||
}
|
||||
|
||||
@ -156,7 +156,7 @@ bool GetVirtualDir(VirtualDir* vdir, const char* path) {
|
||||
bool GetVirtualFilename(char* name, const VirtualFile* vfile, u32 n_chars) {
|
||||
if (vfile->flags & VRT_GAME) return GetVGameFilename(name, vfile, n_chars);
|
||||
else if (vfile->flags & VRT_VRAM) return GetVVramFilename(name, vfile);
|
||||
|
||||
|
||||
strncpy(name, vfile->name, n_chars);
|
||||
return true;
|
||||
}
|
||||
@ -168,7 +168,7 @@ int ReadVirtualFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 coun
|
||||
else if ((offset + count) > vfile->size)
|
||||
count = vfile->size - offset;
|
||||
if (bytes_read) *bytes_read = count;
|
||||
|
||||
|
||||
if (vfile->flags & (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND|VRT_XORPAD)) {
|
||||
return ReadVNandFile(vfile, buffer, offset, count);
|
||||
} else if (vfile->flags & VRT_MEMORY) {
|
||||
@ -186,7 +186,7 @@ int ReadVirtualFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 coun
|
||||
} else if (vfile->flags & VRT_DISADIFF) {
|
||||
return ReadVDisaDiffFile(vfile, buffer, offset, count);
|
||||
}
|
||||
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -197,7 +197,7 @@ int WriteVirtualFile(VirtualFile* vfile, const void* buffer, u64 offset, u64 cou
|
||||
else if (!(vfile->flags & VRT_BDRI) && ((offset + count) > vfile->size))
|
||||
count = vfile->size - offset;
|
||||
if (bytes_written) *bytes_written = count;
|
||||
|
||||
|
||||
if (vfile->flags & VFLAG_READONLY) {
|
||||
return -1;
|
||||
} else if (vfile->flags & (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND)) {
|
||||
@ -211,30 +211,30 @@ int WriteVirtualFile(VirtualFile* vfile, const void* buffer, u64 offset, u64 cou
|
||||
} else if (vfile->flags & VRT_BDRI) {
|
||||
return WriteVBDRIFile(vfile, buffer, offset, count);
|
||||
} // no write support for virtual game / keydb / vram files
|
||||
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int DeleteVirtualFile(const VirtualFile* vfile) {
|
||||
if (!(vfile->flags & VFLAG_DELETABLE)) return -1;
|
||||
|
||||
|
||||
// Special handling for deleting BDRI entries
|
||||
if (vfile->flags & VRT_BDRI)
|
||||
return DeleteVBDRIFile(vfile);
|
||||
|
||||
|
||||
// For anything else, "deleting" is just filling with 0s
|
||||
u32 zeroes_size = STD_BUFFER_SIZE;
|
||||
u8* zeroes = (u8*) malloc(zeroes_size);
|
||||
if (!zeroes) return -1;
|
||||
memset(zeroes, 0x00, zeroes_size);
|
||||
|
||||
|
||||
int result = 0;
|
||||
for (u64 pos = 0; pos < vfile->size; pos += zeroes_size) {
|
||||
u64 wipe_bytes = min(zeroes_size, vfile->size - pos);
|
||||
result = WriteVirtualFile((VirtualFile*)vfile, zeroes, pos, wipe_bytes, NULL);
|
||||
if (result != 0) break;
|
||||
}
|
||||
|
||||
|
||||
free(zeroes);
|
||||
return result;
|
||||
}
|
||||
|
@ -17,28 +17,28 @@ void DeinitVKeyDbDrive(void) {
|
||||
u64 InitVKeyDbDrive(void) { // prerequisite: aeskeydb.bin mounted as image
|
||||
if (!(GetMountState() & BIN_KEYDB)) return 0;
|
||||
n_keys = 0;
|
||||
|
||||
|
||||
// sanity check
|
||||
u64 fsize = GetMountSize();
|
||||
if (!fsize || (fsize % sizeof(AesKeyInfo)) || (fsize > VKEYDB_BUFFER_SIZE)) {
|
||||
n_keys = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// setup vkeydb buffer
|
||||
DeinitVKeyDbDrive(); // dangerous shit
|
||||
key_info = (AesKeyInfo*) malloc(VKEYDB_BUFFER_SIZE);
|
||||
if (!key_info) return 0;
|
||||
|
||||
|
||||
// load the full database into memory
|
||||
n_keys = fsize / sizeof(AesKeyInfo);
|
||||
if (ReadImageBytes((u8*) key_info, 0, fsize) != 0) n_keys = 0;
|
||||
|
||||
|
||||
// decrypt keys if required
|
||||
for (u32 i = 0; i < n_keys; i++) {
|
||||
if (key_info[i].isEncrypted) CryptAesKeyInfo(&(key_info[i]));
|
||||
}
|
||||
|
||||
|
||||
if (!n_keys) DeinitVKeyDbDrive();
|
||||
return (n_keys) ? BIN_KEYDB : 0;
|
||||
}
|
||||
@ -59,17 +59,17 @@ bool ReadVKeyDbDir(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
(key_entry->keyUnitType == KEYS_RETAIL) ? ".ret" : "";
|
||||
snprintf(typestr, 12 + 1, "%s%.10s", (key_entry->type == 'I') ? "IV" :
|
||||
(key_entry->type == 'X') ? "X" : (key_entry->type == 'Y') ? "Y" : "", key_entry->id);
|
||||
|
||||
|
||||
memset(vfile, 0, sizeof(VirtualFile));
|
||||
snprintf(vfile->name, 32, NAME_LEGKEY, keyslot, typestr, unitext);
|
||||
vfile->offset = vdir->index * sizeof(AesKeyInfo);
|
||||
vfile->size = 16; // standard size of a key
|
||||
vfile->keyslot = 0xFF;
|
||||
vfile->flags = VFLAG_READONLY;
|
||||
|
||||
|
||||
return true; // found
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -88,23 +88,23 @@ static const VirtualFile vMemFileTemplates[] = {
|
||||
bool ReadVMemDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir object generated in virtual.c
|
||||
int n_templates = sizeof(vMemFileTemplates) / sizeof(VirtualFile);
|
||||
const VirtualFile* templates = vMemFileTemplates;
|
||||
|
||||
|
||||
while (++vdir->index < n_templates) {
|
||||
// copy current template to vfile
|
||||
memcpy(vfile, templates + vdir->index, sizeof(VirtualFile));
|
||||
|
||||
|
||||
// process special flags
|
||||
if (((vfile->flags & VFLAG_N3DS_EXT) && (IS_O3DS)) || // this is not on O3DS consoles and locked by sighax
|
||||
((vfile->flags & VFLAG_OTP) && !(IS_UNLOCKED)) || // OTP still locked
|
||||
((vfile->flags & VFLAG_BOOT9) && !(HAS_BOOT9)) || // boot9 not found
|
||||
((vfile->flags & VFLAG_BOOT11) && !(HAS_BOOT11)) || // boot11 not found
|
||||
((vfile->flags & VFLAG_OTP_KEY) && !(HAS_OTP_KEY))) // OTP key not found
|
||||
continue;
|
||||
|
||||
continue;
|
||||
|
||||
// found if arriving here
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ int ReadVMemOTPDecrypted(const VirtualFile* vfile, void* buffer, u64 offset, u64
|
||||
alignas(32) u8 otp_iv[0x10];
|
||||
alignas(32) u8 otp_key[0x10];
|
||||
u8* otp_mem = (u8*) __OTP_ADDR;
|
||||
|
||||
|
||||
if (HAS_BOOT9) { // easy setup when boot9 available
|
||||
memcpy(otp_iv, OTP_IV, 0x10);
|
||||
memcpy(otp_key, OTP_KEY, 0x10);
|
||||
@ -125,7 +125,7 @@ int ReadVMemOTPDecrypted(const VirtualFile* vfile, void* buffer, u64 offset, u64
|
||||
(LoadKeyFromFile(otp_iv , 0x11, 'I', "OTP") != 0))
|
||||
return 1; // crypto not available
|
||||
}
|
||||
|
||||
|
||||
setup_aeskey(0x11, otp_key);
|
||||
use_aeskey(0x11);
|
||||
cbc_decrypt(otp_mem, otp_local, __OTP_LEN / 0x10, AES_CNT_TITLEKEY_DECRYPT_MODE, otp_iv);
|
||||
@ -149,7 +149,7 @@ int ReadVMemMCURegisters(const VirtualFile* vfile, void* buffer, u64 offset, u64
|
||||
int ReadVMemFlashCID(const VirtualFile* vfile, void* buffer, u64 offset, u64 count) {
|
||||
// NAND CID if keyslot field != 0.
|
||||
bool is_nand = (bool)vfile->keyslot;
|
||||
|
||||
|
||||
u32 cid[4]; // CID is 16 byte in size
|
||||
sdmmc_get_cid(is_nand, (u32*) cid);
|
||||
memcpy(buffer, ((u8*) cid) + offset, count);
|
||||
@ -160,7 +160,7 @@ int ReadVMemFlashCID(const VirtualFile* vfile, void* buffer, u64 offset, u64 cou
|
||||
int ReadVMemNVRAM(const VirtualFile* vfile, void* buffer, u64 offset, u64 count) {
|
||||
static bool wififlash_initialized = false;
|
||||
(void) vfile;
|
||||
|
||||
|
||||
if (!wififlash_initialized) {
|
||||
wififlash_initialized = spiflash_get_status();
|
||||
if (!wififlash_initialized) return 1;
|
||||
|
@ -54,11 +54,11 @@ bool ReadVNandDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir
|
||||
int n_templates = sizeof(vNandTemplates) / sizeof(VirtualNandTemplate);
|
||||
const VirtualNandTemplate* templates = vNandTemplates;
|
||||
u32 nand_src = vdir->flags & VRT_SOURCE;
|
||||
|
||||
|
||||
while (++vdir->index < n_templates) {
|
||||
const VirtualNandTemplate* template = templates + vdir->index;
|
||||
NandPartitionInfo prt_info;
|
||||
|
||||
|
||||
// set up virtual file
|
||||
if (template->flags & VFLAG_NAND_SIZE) { // override for "nand.bin"
|
||||
prt_info.sector = 0;
|
||||
@ -71,7 +71,7 @@ bool ReadVNandDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir
|
||||
vfile->size = ((u64) prt_info.count) * 0x200;
|
||||
vfile->keyslot = prt_info.keyslot;
|
||||
vfile->flags = template->flags;
|
||||
|
||||
|
||||
// handle special cases
|
||||
if (!vfile->size) continue;
|
||||
if ((nand_src == VRT_XORPAD) && ((vfile->keyslot == 0x11) || (vfile->keyslot >= 0x40)))
|
||||
@ -98,12 +98,12 @@ bool ReadVNandDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir
|
||||
if (sha_cmp(perfect_sha, keydb, KEYDB_PERFECT_SIZE, SHA256_MODE) != 0) continue;
|
||||
vfile->size = KEYDB_PERFECT_SIZE;
|
||||
}
|
||||
|
||||
|
||||
// found if arriving here
|
||||
vfile->flags |= nand_src;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -5,13 +5,13 @@
|
||||
bool SplitTarFName(char* tar_fname, char** dir, char** name) {
|
||||
u32 len = strnlen(tar_fname, 100 + 1);
|
||||
if (!len || (len == 101)) return false;
|
||||
|
||||
|
||||
// remove trailing slash
|
||||
if (tar_fname[len-1] == '/') tar_fname[--len] = '\0';
|
||||
|
||||
|
||||
// find last slash
|
||||
char* slash = strrchr(tar_fname, '/');
|
||||
|
||||
|
||||
// relative root dir entry
|
||||
if (!slash) {
|
||||
*name = tar_fname;
|
||||
@ -21,7 +21,7 @@ bool SplitTarFName(char* tar_fname, char** dir, char** name) {
|
||||
*name = slash + 1;
|
||||
*dir = tar_fname;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -34,8 +34,8 @@ bool ReadVVramDir(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
vfile->name[0] = '\0';
|
||||
vfile->flags = VFLAG_READONLY;
|
||||
vfile->keyslot = 0xFF;
|
||||
|
||||
|
||||
|
||||
|
||||
// get current dir name
|
||||
char curr_dir[100 + 1];
|
||||
if (vdir->offset == (u64) -1) return false; // end of the dir?
|
||||
@ -48,47 +48,47 @@ bool ReadVVramDir(VirtualFile* vfile, VirtualDir* vdir) {
|
||||
if (len == 101) return false; // path error
|
||||
if (curr_dir[len-1] == '/') curr_dir[len-1] = '\0';
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// using vdir index to signify the position limits us to 1TiB TARs
|
||||
void* tardata = NULL;
|
||||
if (vdir->index < 0) tardata = FirstVTarEntry();
|
||||
else tardata = NextVTarEntry(OffsetVTarEntry(vdir->index << 9));
|
||||
|
||||
|
||||
if (tardata) do {
|
||||
TarHeader* tar = (TarHeader*) tardata;
|
||||
char tar_fname[100 + 1];
|
||||
char *name, *dir;
|
||||
|
||||
|
||||
strncpy(tar_fname, tar->fname, 100);
|
||||
if (!SplitTarFName(tar_fname, &dir, &name)) return false;
|
||||
if ((!dir && !*curr_dir) || (dir && (strncmp(dir, curr_dir, 100) == 0))) break;
|
||||
} while ((tardata = NextVTarEntry(tardata)));
|
||||
|
||||
|
||||
// match found?
|
||||
if (tardata) {
|
||||
u64 fsize;
|
||||
bool is_dir;
|
||||
void* fdata = GetVTarFileInfo(tardata, NULL, &fsize, &is_dir);
|
||||
|
||||
|
||||
vfile->offset = (u32) fdata - VRAM0_OFFSET;
|
||||
vfile->size = fsize;
|
||||
if (is_dir) vfile->flags |= VFLAG_DIR;
|
||||
|
||||
|
||||
vdir->index = (vfile->offset >> 9) - 1;
|
||||
} else { // not found
|
||||
vdir->offset = (u64) -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int ReadVVramFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count) {
|
||||
if (vfile->flags & VFLAG_DIR) return -1;
|
||||
void* fdata = (u8*) VRAM0_OFFSET + vfile->offset;
|
||||
|
||||
|
||||
// range checks in virtual.c
|
||||
memcpy(buffer, (u8*) fdata + offset, count);
|
||||
return 0;
|
||||
@ -99,11 +99,11 @@ bool GetVVramFilename(char* name, const VirtualFile* vfile) {
|
||||
TarHeader* tar = (TarHeader*) tardata;
|
||||
char tar_fname[100 + 1];
|
||||
char *name_tmp, *dir;
|
||||
|
||||
|
||||
strncpy(tar_fname, tar->fname, 100);
|
||||
if (!SplitTarFName(tar_fname, &dir, &name_tmp)) return false;
|
||||
strncpy(name, name_tmp, 100);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@ bool MatchVVramFilename(const char* name, const VirtualFile* vfile) {
|
||||
TarHeader* tar = (TarHeader*) tardata;
|
||||
char tar_fname[100 + 1];
|
||||
char *name_tmp, *dir;
|
||||
|
||||
|
||||
strncpy(tar_fname, tar->fname, 100);
|
||||
if (!SplitTarFName(tar_fname, &dir, &name_tmp)) return false;
|
||||
return (strncasecmp(name, name_tmp, 100) == 0);
|
||||
|
Loading…
x
Reference in New Issue
Block a user