mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 13:42:47 +00:00
Allow (batch) verification of NCCH / NCSD / CIA
This commit is contained in:
parent
2e8f6405e7
commit
db8bbdb224
@ -163,7 +163,7 @@ void cbc_decrypt(void *inbuf, void *outbuf, size_t size, uint32_t mode, uint8_t
|
||||
set_ctr(ctr);
|
||||
blocks = (blocks_left >= 0xFFFF) ? 0xFFFF : blocks_left;
|
||||
for (i=0; i<AES_BLOCK_SIZE; i++)
|
||||
ctr[i] = in[(blocks - 1) * AES_BLOCK_SIZE] + i;
|
||||
ctr[i] = in[((blocks - 1) * AES_BLOCK_SIZE) + i];
|
||||
aes_decrypt(in, out, blocks, mode);
|
||||
in += blocks * AES_BLOCK_SIZE;
|
||||
out += blocks * AES_BLOCK_SIZE;
|
||||
|
@ -85,6 +85,12 @@ u32 GetTitleKey(u8* titlekey, Ticket* ticket) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 GetCiaCtr(u8* ctr, TmdContentChunk* chunk) {
|
||||
memset(ctr, 0, 16);
|
||||
memcpy(ctr, chunk->index, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 BuildCiaCert(u8* ciacert) {
|
||||
const u8 cert_hash_expected[0x20] = {
|
||||
0xC7, 0x2E, 0x1C, 0xA5, 0x61, 0xDC, 0x9B, 0xC8, 0x05, 0x58, 0x58, 0x9C, 0x63, 0x08, 0x1C, 0x8A,
|
||||
@ -165,30 +171,13 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 LoadCiaStub(CiaStub* stub, const char* path) {
|
||||
FIL file;
|
||||
UINT bytes_read;
|
||||
CiaInfo info;
|
||||
|
||||
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
|
||||
// first 0x20 byte of CIA header
|
||||
f_lseek(&file, 0);
|
||||
if ((f_read(&file, stub, 0x20, &bytes_read) != FR_OK) || (bytes_read != 0x20) ||
|
||||
(ValidateCiaHeader(&(stub->header)) != 0)) {
|
||||
f_close(&file);
|
||||
return 1;
|
||||
}
|
||||
GetCiaInfo(&info, &(stub->header));
|
||||
|
||||
// everything up till content offset
|
||||
f_lseek(&file, 0);
|
||||
if ((f_read(&file, stub, info.offset_content, &bytes_read) != FR_OK) || (bytes_read != info.offset_content)) {
|
||||
f_close(&file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
f_close(&file);
|
||||
u32 DecryptCiaContent(u8* data, u32 size, u8* ctr, const u8* titlekey) {
|
||||
// WARNING: size and offset of data have to be a multiple of 16
|
||||
u8 tik[16] __attribute__((aligned(32)));
|
||||
u32 mode = AES_CNT_TITLEKEY_DECRYPT_MODE;
|
||||
memcpy(tik, titlekey, 16);
|
||||
setup_aeskey(0x11, tik);
|
||||
use_aeskey(0x11);
|
||||
cbc_decrypt(data, data, size / 16, mode, ctr);
|
||||
return 0;
|
||||
}
|
||||
|
@ -147,6 +147,7 @@ u32 ValidateCiaHeader(CiaHeader* header);
|
||||
u32 GetCiaInfo(CiaInfo* info, CiaHeader* header);
|
||||
u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd);
|
||||
u32 GetTitleKey(u8* titlekey, Ticket* ticket);
|
||||
u32 GetCiaCtr(u8* ctr, TmdContentChunk* chunk);
|
||||
|
||||
u32 BuildCiaCert(u8* ciacert);
|
||||
u32 BuildFakeTicket(Ticket* ticket, u8* title_id);
|
||||
@ -155,4 +156,4 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents);
|
||||
/*u32 BuildCiaMeta(Ncch ncch);
|
||||
u32 InsertCiaContent(const char* path, const char* content, bool encrypt);*/
|
||||
|
||||
u32 LoadCiaStub(CiaStub* stub, const char* path);
|
||||
u32 DecryptCiaContent(u8* data, u32 size, u8* ctr, const u8* titlekey);
|
||||
|
276
source/game/gameio.c
Normal file
276
source/game/gameio.c
Normal file
@ -0,0 +1,276 @@
|
||||
#include "gameio.h"
|
||||
#include "game.h"
|
||||
#include "ui.h"
|
||||
#include "filetype.h"
|
||||
#include "sddata.h"
|
||||
#include "aes.h"
|
||||
#include "sha.h"
|
||||
#include "ff.h"
|
||||
|
||||
u32 GetNcchFileHeaders(NcchHeader* ncch, ExeFsHeader* exefs, FIL* file) {
|
||||
u32 offset_ncch = f_tell(file);
|
||||
UINT btr;
|
||||
|
||||
if ((fx_read(file, ncch, sizeof(NcchHeader), &btr) != FR_OK) ||
|
||||
(ValidateNcchHeader(ncch) != 0))
|
||||
return 1;
|
||||
|
||||
if (exefs && ncch->size_exefs) {
|
||||
u32 offset_exefs = offset_ncch + (ncch->offset_exefs * NCCH_MEDIA_UNIT);
|
||||
f_lseek(file, offset_exefs);
|
||||
if ((fx_read(file, exefs, sizeof(ExeFsHeader), &btr) != FR_OK) ||
|
||||
(DecryptNcch((u8*) exefs, ncch->offset_exefs * NCCH_MEDIA_UNIT, sizeof(ExeFsHeader), ncch, NULL) != 0) ||
|
||||
(ValidateExeFsHeader(exefs, ncch->size_exefs * NCCH_MEDIA_UNIT) != 0))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 CheckNcchFileHash(u8* expected, FIL* file, u32 size_data, u32 offset_ncch, NcchHeader* ncch, ExeFsHeader* exefs) {
|
||||
u32 offset_data = f_tell(file) - offset_ncch;
|
||||
u8 hash[32];
|
||||
|
||||
sha_init(SHA256_MODE);
|
||||
for (u32 i = 0; i < size_data; i += MAIN_BUFFER_SIZE) {
|
||||
u32 read_bytes = min(MAIN_BUFFER_SIZE, (size_data - i));
|
||||
UINT bytes_read;
|
||||
fx_read(file, MAIN_BUFFER, read_bytes, &bytes_read);
|
||||
DecryptNcch(MAIN_BUFFER, offset_data + i, read_bytes, ncch, exefs);
|
||||
sha_update(MAIN_BUFFER, read_bytes);
|
||||
}
|
||||
sha_get(hash);
|
||||
|
||||
return (memcmp(hash, expected, 32) == 0) ? 0 : 1;
|
||||
}
|
||||
|
||||
u32 VerifyNcchFile(const char* path, u32 offset, u32 size) {
|
||||
NcchHeader ncch;
|
||||
ExeFsHeader exefs;
|
||||
FIL file;
|
||||
|
||||
char pathstr[32 + 1];
|
||||
TruncateString(pathstr, path, 32, 8);
|
||||
|
||||
// open file, get NCCH, ExeFS header
|
||||
if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
f_lseek(&file, offset);
|
||||
|
||||
if (GetNcchFileHeaders(&ncch, &exefs, &file) != 0) {
|
||||
if (!offset) ShowPrompt(false, "%s\nError: Not a NCCH file", pathstr);
|
||||
fx_close(&file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// size checks
|
||||
if (!size) size = f_size(&file) - offset;
|
||||
if ((f_size(&file) < offset) || (size < ncch.size * NCCH_MEDIA_UNIT)) {
|
||||
if (!offset) ShowPrompt(false, "%s\nError: File is too small", pathstr);
|
||||
fx_close(&file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// check / setup crypto
|
||||
if (SetupNcchCrypto(&ncch) != 0) {
|
||||
if (!offset) ShowPrompt(false, "%s\nError: Crypto not set up", pathstr);
|
||||
fx_close(&file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
u32 ver_exthdr = 0;
|
||||
u32 ver_exefs = 0;
|
||||
u32 ver_romfs = 0;
|
||||
|
||||
// base hash check for extheader
|
||||
if (ncch.size_exthdr > 0) {
|
||||
f_lseek(&file, offset + NCCH_EXTHDR_OFFSET);
|
||||
ver_exthdr = CheckNcchFileHash(ncch.hash_exthdr, &file, 0x400, offset, &ncch, &exefs);
|
||||
}
|
||||
|
||||
// base hash check for exefs
|
||||
if (ncch.size_exefs > 0) {
|
||||
f_lseek(&file, offset + (ncch.offset_exefs * NCCH_MEDIA_UNIT));
|
||||
ver_exefs = CheckNcchFileHash(ncch.hash_exefs, &file, ncch.size_exefs_hash * NCCH_MEDIA_UNIT, offset, &ncch, &exefs);
|
||||
}
|
||||
|
||||
// base hash check for romfs
|
||||
if (ncch.size_romfs > 0) {
|
||||
f_lseek(&file, offset + (ncch.offset_romfs * NCCH_MEDIA_UNIT));
|
||||
ver_romfs = CheckNcchFileHash(ncch.hash_romfs, &file, ncch.size_romfs_hash * NCCH_MEDIA_UNIT, offset, &ncch, &exefs);
|
||||
}
|
||||
|
||||
// thorough exefs verification
|
||||
if (ncch.size_exefs > 0) {
|
||||
for (u32 i = 0; !ver_exefs && (i < 10); i++) {
|
||||
ExeFsFileHeader* exefile = exefs.files + i;
|
||||
u8* hash = exefs.hashes[9 - i];
|
||||
if (!exefile->size) continue;
|
||||
f_lseek(&file, offset + (ncch.offset_exefs * NCCH_MEDIA_UNIT) + 0x200 + exefile->offset);
|
||||
ver_exefs = CheckNcchFileHash(hash, &file, exefile->size, offset, &ncch, &exefs);
|
||||
}
|
||||
}
|
||||
|
||||
if (!offset && (ver_exthdr|ver_exefs|ver_romfs)) { // verification summary
|
||||
ShowPrompt(false, "%s\nNCCH verification failed:\nExtHdr/ExeFS/RomFS: %s/%s/%s", pathstr,
|
||||
(!ncch.size_exthdr) ? "-" : (ver_exthdr == 0) ? "ok" : "fail",
|
||||
(!ncch.size_exefs) ? "-" : (ver_exefs == 0) ? "ok" : "fail",
|
||||
(!ncch.size_romfs) ? "-" : (ver_romfs == 0) ? "ok" : "fail");
|
||||
}
|
||||
|
||||
fx_close(&file);
|
||||
return ver_exthdr|ver_exefs|ver_romfs;
|
||||
}
|
||||
|
||||
u32 LoadNcsdHeader(NcsdHeader* ncsd, const char* path) {
|
||||
FIL file;
|
||||
UINT btr;
|
||||
|
||||
// open file, get NCSD header
|
||||
if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
f_lseek(&file, 0);
|
||||
if ((fx_read(&file, ncsd, sizeof(NcsdHeader), &btr) != FR_OK) ||
|
||||
(ValidateNcsdHeader(ncsd) != 0)) {
|
||||
fx_close(&file);
|
||||
return 1;
|
||||
}
|
||||
fx_close(&file);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 VerifyNcsdFile(const char* path) {
|
||||
NcsdHeader ncsd;
|
||||
|
||||
// path string
|
||||
char pathstr[32 + 1];
|
||||
TruncateString(pathstr, path, 32, 8);
|
||||
|
||||
// load NCSD header
|
||||
if (LoadNcsdHeader(&ncsd, path) != 0) {
|
||||
ShowPrompt(false, "%s\nError: Not a NCSD file", pathstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// validate NCSD contents
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
NcchPartition* partition = ncsd.partitions + i;
|
||||
u32 offset = partition->offset * NCSD_MEDIA_UNIT;
|
||||
u32 size = partition->size * NCSD_MEDIA_UNIT;
|
||||
if (!size) continue;
|
||||
if (VerifyNcchFile(path, offset, size) != 0) {
|
||||
ShowPrompt(false, "%s\nContent%lu (%08lX@%08lX):\nVerification failed",
|
||||
pathstr, i, size, offset, i);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 LoadCiaStub(CiaStub* stub, const char* path) {
|
||||
FIL file;
|
||||
UINT btr;
|
||||
CiaInfo info;
|
||||
|
||||
if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
|
||||
// first 0x20 byte of CIA header
|
||||
f_lseek(&file, 0);
|
||||
if ((fx_read(&file, stub, 0x20, &btr) != FR_OK) || (btr != 0x20) ||
|
||||
(ValidateCiaHeader(&(stub->header)) != 0)) {
|
||||
fx_close(&file);
|
||||
return 1;
|
||||
}
|
||||
GetCiaInfo(&info, &(stub->header));
|
||||
|
||||
// everything up till content offset
|
||||
f_lseek(&file, 0);
|
||||
if ((fx_read(&file, stub, info.offset_content, &btr) != FR_OK) || (btr != info.offset_content)) {
|
||||
fx_close(&file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fx_close(&file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 VerifyCiaContent(const char* path, u64 offset, TmdContentChunk* chunk, const u8* titlekey) {
|
||||
u8 hash[32];
|
||||
u8 ctr[16];
|
||||
FIL file;
|
||||
|
||||
u8* expected = chunk->hash;
|
||||
u64 size = getbe64(chunk->size);
|
||||
bool encrypted = getbe16(chunk->type) & 0x1;
|
||||
|
||||
if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
if (offset + size > f_size(&file)) {
|
||||
fx_close(&file);
|
||||
return 1;
|
||||
}
|
||||
f_lseek(&file, offset);
|
||||
|
||||
GetCiaCtr(ctr, chunk);
|
||||
sha_init(SHA256_MODE);
|
||||
ShowProgress(0, 0, path);
|
||||
for (u32 i = 0; i < size; i += MAIN_BUFFER_SIZE) {
|
||||
u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i));
|
||||
UINT bytes_read;
|
||||
fx_read(&file, MAIN_BUFFER, read_bytes, &bytes_read);
|
||||
if (encrypted) DecryptCiaContent(MAIN_BUFFER, read_bytes, ctr, titlekey);
|
||||
sha_update(MAIN_BUFFER, read_bytes);
|
||||
if (!ShowProgress(i + read_bytes, size, path)) break;
|
||||
}
|
||||
sha_get(hash);
|
||||
fx_close(&file);
|
||||
|
||||
return memcmp(hash, expected, 32);
|
||||
}
|
||||
|
||||
u32 VerifyCiaFile(const char* path) {
|
||||
CiaStub* cia = (CiaStub*) TEMP_BUFFER;
|
||||
CiaInfo info;
|
||||
u8 titlekey[16];
|
||||
|
||||
// path string
|
||||
char pathstr[32 + 1];
|
||||
TruncateString(pathstr, path, 32, 8);
|
||||
|
||||
// load CIA stub
|
||||
if ((LoadCiaStub(cia, path) != 0) ||
|
||||
(GetCiaInfo(&info, &(cia->header)) != 0) ||
|
||||
(GetTitleKey(titlekey, &(cia->ticket)) != 0)) {
|
||||
ShowPrompt(false, "%s\nError: Probably not a CIA file", pathstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// verify contents
|
||||
u32 content_count = getbe16(cia->tmd.content_count);
|
||||
u64 next_offset = info.offset_content;
|
||||
for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) {
|
||||
TmdContentChunk* chunk = &(cia->content_list[i]);
|
||||
if (VerifyCiaContent(path, next_offset, chunk, titlekey) != 0) {
|
||||
ShowPrompt(false, "%s\nID %08lX (%08llX@%08llX)\nVerification failed",
|
||||
pathstr, getbe32(chunk->id), getbe64(chunk->size), next_offset, i);
|
||||
return 1;
|
||||
}
|
||||
next_offset += getbe64(chunk->size);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 VerifyGameFile(const char* path) {
|
||||
u32 filetype = IdentifyFileType(path);
|
||||
if (filetype == GAME_CIA)
|
||||
return VerifyCiaFile(path);
|
||||
else if (filetype == GAME_NCSD)
|
||||
return VerifyNcsdFile(path);
|
||||
else if (filetype == GAME_NCCH)
|
||||
return VerifyNcchFile(path, 0, 0);
|
||||
else return 1;
|
||||
}
|
12
source/game/gameio.h
Normal file
12
source/game/gameio.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
|
||||
u32 VerifyNcchFile(const char* path, u32 offset, u32 size);
|
||||
u32 VerifyNcsdFile(const char* path);
|
||||
u32 VerifiyCiaFile(const char* path);
|
||||
u32 VerifyGameFile(const char* path);
|
||||
|
||||
u32 DecryptNcchFile(const char* path, u32 offset, u32 size);
|
||||
u32 DecryptNcsdFile(const char* path);
|
||||
u32 DecryptCiaFile(const char* path, bool deep);
|
@ -9,8 +9,8 @@ u32 ValidateNcsdHeader(NcsdHeader* header) {
|
||||
u32 data_units = 0;
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
NcchPartition* partition = header->partitions + i;
|
||||
if ((partition->offset == 0) && (partition->size == 0))
|
||||
continue;
|
||||
if ((i == 0) && !partition->size) return 1; // first content must be there
|
||||
else if (!partition->size) continue;
|
||||
if (partition->offset < data_units)
|
||||
return 1; // overlapping partitions, failed
|
||||
data_units = partition->offset + partition->size;
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "ui.h"
|
||||
#include "hid.h"
|
||||
#include "fs.h"
|
||||
#include "gameio.h"
|
||||
#include "platform.h"
|
||||
#include "nand.h"
|
||||
#include "virtual.h"
|
||||
@ -660,35 +661,44 @@ u32 GodMode() {
|
||||
}
|
||||
} else if ((pad_state & BUTTON_A) && (curr_entry->type == T_FILE)) { // process a file
|
||||
char pathstr[32 + 1];
|
||||
const char* optionstr[5];
|
||||
u32 n_opt = 0;
|
||||
const char* optionstr[8];
|
||||
int n_opt = 0;
|
||||
|
||||
u32 filetype = IdentifyFileType(curr_entry->path);
|
||||
u32 drvtype = DriveType(curr_entry->path);
|
||||
|
||||
int mountable = (filetype && (drvtype & DRV_FAT) &&
|
||||
!(drvtype & (DRV_IMAGE|DRV_RAMDRIVE))) ?
|
||||
(int) ++n_opt : -1;
|
||||
int hexviewer = (int) ++n_opt;
|
||||
int calcsha = (int) ++n_opt;
|
||||
++n_opt : -1;
|
||||
int hexviewer = ++n_opt;
|
||||
int calcsha = ++n_opt;
|
||||
int verificable = ((filetype == GAME_CIA) || (filetype == GAME_NCSD) ||
|
||||
(filetype == GAME_NCCH)) ? ++n_opt : -1;
|
||||
int injectable = ((clipboard->n_entries == 1) &&
|
||||
(clipboard->entry[0].type == T_FILE) &&
|
||||
(drvtype & DRV_FAT) &&
|
||||
(strncmp(clipboard->entry[0].path, curr_entry->path, 256) != 0)) ?
|
||||
(int) ++n_opt : -1;
|
||||
int searchdrv = (curr_drvtype & DRV_SEARCH) ? (int) ++n_opt : -1;
|
||||
int searchdrv = (curr_drvtype & DRV_SEARCH) ? ++n_opt : -1;
|
||||
|
||||
u32 n_marked = 0;
|
||||
if (curr_entry->marked) {
|
||||
for (u32 i = 0; i < current_dir->n_entries; i++)
|
||||
if (current_dir->entry[i].marked) n_marked++;
|
||||
}
|
||||
|
||||
TruncateString(pathstr, curr_entry->path, 32, 8);
|
||||
optionstr[hexviewer-1] = "Show in Hexeditor";
|
||||
optionstr[calcsha-1] = "Calculate SHA-256";
|
||||
if (injectable) optionstr[injectable-1] = "Inject data @offset";
|
||||
if (mountable) optionstr[mountable-1] =
|
||||
if (verificable > 0) optionstr[verificable-1] = "Verify game image";
|
||||
if (injectable > 0) optionstr[injectable-1] = "Inject data @offset";
|
||||
if (mountable > 0) optionstr[mountable-1] =
|
||||
(filetype == IMG_NAND) ? "Mount as NAND image" :
|
||||
(filetype == IMG_FAT) ? "Mount as FAT image" :
|
||||
(filetype == GAME_CIA) ? "Mount as CIA image" :
|
||||
(filetype == GAME_NCSD) ? "Mount as NCSD image" :
|
||||
(filetype == GAME_NCCH) ? "Mount as NCCH image" : "???";
|
||||
if (searchdrv) optionstr[searchdrv-1] = "Open containing folder";
|
||||
if (searchdrv > 0) optionstr[searchdrv-1] = "Open containing folder";
|
||||
|
||||
int user_select = ShowSelectPrompt(n_opt, optionstr, pathstr);
|
||||
if (user_select == hexviewer) { // -> show in hex viewer
|
||||
@ -696,6 +706,33 @@ u32 GodMode() {
|
||||
} else if (user_select == calcsha) { // -> calculate SHA-256
|
||||
Sha256Calculator(curr_entry->path);
|
||||
GetDirContents(current_dir, current_path);
|
||||
} else if (user_select == verificable) { // -> verify game file
|
||||
if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) {
|
||||
u32 n_success = 0;
|
||||
u32 n_other = 0;
|
||||
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)
|
||||
continue;
|
||||
if (IdentifyFileType(path) != filetype) {
|
||||
n_other++;
|
||||
continue;
|
||||
}
|
||||
if ((filetype != GAME_CIA) && !ShowProgress(n_processed++, n_marked, path)) break;
|
||||
if (VerifyGameFile(path) == 0) n_success++;
|
||||
else if (filetype != GAME_CIA) ShowProgress(0, 0, path); // redraw progress bar
|
||||
current_dir->entry[i].marked = false;
|
||||
}
|
||||
if (filetype != GAME_CIA) ShowProgress(1, 1, ""); // CIA verification has progress bar handling
|
||||
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 {
|
||||
ShowString("%s\nVerifying file, please wait...", pathstr);
|
||||
u32 result = VerifyGameFile(curr_entry->path);
|
||||
ShowPrompt(false, "%s\nVerification %s", pathstr, (result == 0) ? "success" : "failed");
|
||||
}
|
||||
} else if (user_select == injectable) { // -> inject data from clipboard
|
||||
char origstr[18 + 1];
|
||||
TruncateString(origstr, clipboard->entry[0].name, 18, 10);
|
||||
|
@ -536,8 +536,9 @@ bool ShowProgress(u64 current, u64 total, const char* opstr)
|
||||
}
|
||||
DrawRectangle(TOP_SCREEN, bar_pos_x + 2, bar_pos_y + 2, prog_width, bar_height - 4, COLOR_STD_FONT);
|
||||
|
||||
TruncateString(tempstr, opstr, (bar_width / FONT_WIDTH_EXT) - 7, 8);
|
||||
snprintf(progstr, 64, "%s (%lu%%)", tempstr, prog_percent);
|
||||
TruncateString(progstr, opstr, (bar_width / FONT_WIDTH_EXT) - 7, 8);
|
||||
snprintf(tempstr, 64, "%s (%lu%%)", progstr, prog_percent);
|
||||
ResizeString(progstr, tempstr, bar_width / FONT_WIDTH_EXT, 8, false);
|
||||
DrawString(TOP_SCREEN, progstr, bar_pos_x, text_pos_y, COLOR_STD_FONT, COLOR_STD_BG);
|
||||
DrawString(TOP_SCREEN, "(hold B to cancel)", bar_pos_x + 2, text_pos_y + 14, COLOR_STD_FONT, COLOR_STD_BG);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user