diff --git a/file.c b/file.c index de2865a..ff380b6 100644 --- a/file.c +++ b/file.c @@ -20,6 +20,7 @@ #include "archive.h" #include "file.h" #include "utils.h" +#include "sha1.h" static char *mount_points[] = { "app0:", @@ -94,6 +95,59 @@ int getFileSize(char *pInputFileName) return fileSize; } +int getFileSha1(char *pInputFileName, uint8_t *pSha1Out, uint64_t *value, uint64_t max, void (* SetProgress)(uint64_t value, uint64_t max), int (* cancelHandler)()) { + + // Set up SHA1 context + SHA1_CTX ctx; + sha1_init(&ctx); + + // Open the file to read, else return the error + SceUID fd = sceIoOpen(pInputFileName, SCE_O_RDONLY, 0); + if (fd < 0) + return fd; + + // Open up the buffer for copying data into + void *buf = malloc(TRANSFER_SIZE); + + int read; + + // Actually take the SHA1 sum + while ((read = sceIoRead(fd, buf, TRANSFER_SIZE)) > 0) + { + sha1_update(&ctx, buf, read); + + // Defined in io_process.c, check to make sure pointer isn't null before incrementing + if(value) + (*value)++; // Note: Max value is filesize/TRANSFER_SIZE + + if(SetProgress) + SetProgress(value ? *value : 0, max); + + // Check to see if cancelHandler exists, if so call it and free memory if cancelled + if(cancelHandler && cancelHandler()) { + free(buf); + sceIoClose(fd); + return 0; + } + + // This is CPU intensive so the progress bar won't refresh unless we sleep + // DIALOG_WAIT seemed too long for this application + // so I set it to 1/2 of a second every 8192 TRANSFER_SIZE blocks + if((*value)%8192 == 0) + sceKernelDelayThread(500000); + } + + // Final iteration of SHA1 sum, dump final value into pSha1Out buffer + sha1_final(&ctx, pSha1Out); + + // Free up file buffer + free(buf); + + // Close file proper + sceIoClose(fd); + return 1; +} + int getPathInfo(char *path, uint64_t *size, uint32_t *folders, uint32_t *files) { SceUID dfd = sceIoDopen(path); if (dfd >= 0) { diff --git a/file.h b/file.h index d1e3449..dc2961e 100644 --- a/file.h +++ b/file.h @@ -79,6 +79,7 @@ int ReadFile(char *file, void *buf, int size); int WriteFile(char *file, void *buf, int size); int getFileSize(char *pInputFileName); +int getFileSha1(char *pInputFileName, uint8_t *pSha1Out, uint64_t *value, uint64_t max, void (*SetProgress)(uint64_t value, uint64_t max), int (* cancelHandler)()); int getPathInfo(char *path, uint64_t *size, uint32_t *folders, uint32_t *files); int removePath(char *path, uint64_t *value, uint64_t max, void (* SetProgress)(uint64_t value, uint64_t max), int (* cancelHandler)()); int copyFile(char *src_path, char *dst_path, uint64_t *value, uint64_t max, void (* SetProgress)(uint64_t value, uint64_t max), int (* cancelHandler)()); diff --git a/io_process.c b/io_process.c index b63017e..9018731 100644 --- a/io_process.c +++ b/io_process.c @@ -319,4 +319,72 @@ EXIT: powerUnlock(); return sceKernelExitDeleteThread(0); -} \ No newline at end of file +} + +int hash_thread(SceSize args_size, HashArguments *args) { + SceUID thid = -1; + + // Lock power timers + powerLock(); + + // Set progress to 0% + sceMsgDialogProgressBarSetValue(SCE_MSG_DIALOG_PROGRESSBAR_TARGET_BAR_DEFAULT, 0); + sceKernelDelayThread(DIALOG_WAIT); // Needed to see the percentage + + uint64_t max = (uint64_t) (getFileSize(args->file_path)/(TRANSFER_SIZE)); + + // SHA1 process + uint64_t value = 0; + + // Spin off a thread to update the progress dialog + thid = createStartUpdateThread(max); + + uint8_t sha1out[20]; + int res = getFileSha1(args->file_path, sha1out, &value, max, SetProgress, cancelHandler); + if (res <= 0) { + // SHA1 Didn't complete successfully, or was cancelled + closeWaitDialog(); + dialog_step = DIALOG_STEP_CANCELLED; + errorDialog(res); + goto EXIT; + } + + // Since we hit here, we're done. Set progress to 100% + sceMsgDialogProgressBarSetValue(SCE_MSG_DIALOG_PROGRESSBAR_TARGET_BAR_DEFAULT, 100); + sceKernelDelayThread(COUNTUP_WAIT); + + // Close + closeWaitDialog(); + + char sha1msg[41]; + int i; + + // Construct SHA1 sum string + for (i = 0; i < 20; i++) { + sprintf(sha1msg + (2*i), "%02x ", sha1out[i]); + } + + sha1msg[40] = '\0'; + + initMessageDialog(SCE_MSG_DIALOG_BUTTON_TYPE_OK, sha1msg); + dialog_step = DIALOG_STEP_HASH_DISPLAY; + + // Wait for response + while (dialog_step == DIALOG_STEP_HASH_DISPLAY) { + sceKernelDelayThread(1000); + } + + closeWaitDialog(); + sceMsgDialogClose(); + +EXIT: + + // Ensure the update thread ends gracefully + if(thid>=0) + sceKernelWaitThreadEnd(thid, NULL, NULL); + + powerUnlock(); + + // Kill current thread + return sceKernelExitDeleteThread(0); +} diff --git a/io_process.h b/io_process.h index 960d936..59a3ba2 100644 --- a/io_process.h +++ b/io_process.h @@ -45,6 +45,10 @@ typedef struct { int copy_mode; } CopyArguments; +typedef struct { + char *file_path; +} HashArguments; + void closeWaitDialog(); int cancelHandler(); void SetProgress(uint64_t value, uint64_t max); @@ -52,5 +56,6 @@ SceUID createStartUpdateThread(uint64_t max); int delete_thread(SceSize args_size, DeleteArguments *args); int copy_thread(SceSize args_size, CopyArguments *args); +int hash_thread(SceSize args_size, HashArguments *args); -#endif \ No newline at end of file +#endif diff --git a/language.c b/language.c index e297428..4aa8623 100644 --- a/language.c +++ b/language.c @@ -98,6 +98,9 @@ void loadLanguage(int id) { LANGUAGE_ENTRY(SYS_INFO), LANGUAGE_ENTRY(INSTALL_ALL), LANGUAGE_ENTRY(UPDATE_QUESTION), + LANGUAGE_ENTRY(SHA1), + LANGUAGE_ENTRY(HASHING), + LANGUAGE_ENTRY(HASH_FILE_QUESTION), }; // Load default config file diff --git a/language.h b/language.h index ba005c1..8dbb8d5 100644 --- a/language.h +++ b/language.h @@ -56,6 +56,9 @@ enum LanguageContainer { SYS_INFO, INSTALL_ALL, UPDATE_QUESTION, + SHA1, + HASHING, + HASH_FILE_QUESTION, LANGUAGE_CONTRAINER_SIZE, }; diff --git a/main.c b/main.c index 48e1462..99a97f0 100644 --- a/main.c +++ b/main.c @@ -450,6 +450,7 @@ enum MenuEntrys { MENU_ENTRY_RENAME, MENU_ENTRY_EMPTY_4, MENU_ENTRY_NEW_FOLDER, + MENU_ENTRY_SHA1, }; enum MenuVisibilities { @@ -475,6 +476,7 @@ MenuEntry menu_entries[] = { { RENAME, VISIBILITY_INVISIBLE }, { -1, VISIBILITY_UNUSED }, { NEW_FOLDER, VISIBILITY_INVISIBLE }, + { SHA1, VISIBILITY_INVISIBLE } }; #define N_MENU_ENTRIES (sizeof(menu_entries) / sizeof(MenuEntry)) @@ -497,6 +499,7 @@ void initContextMenu() { menu_entries[MENU_ENTRY_COPY].visibility = VISIBILITY_INVISIBLE; menu_entries[MENU_ENTRY_DELETE].visibility = VISIBILITY_INVISIBLE; menu_entries[MENU_ENTRY_RENAME].visibility = VISIBILITY_INVISIBLE; + menu_entries[MENU_ENTRY_SHA1].visibility = VISIBILITY_INVISIBLE; } // Invisible 'Paste' if nothing is copied yet @@ -510,6 +513,11 @@ void initContextMenu() { menu_entries[MENU_ENTRY_DELETE].visibility = VISIBILITY_INVISIBLE; menu_entries[MENU_ENTRY_RENAME].visibility = VISIBILITY_INVISIBLE; menu_entries[MENU_ENTRY_NEW_FOLDER].visibility = VISIBILITY_INVISIBLE; + menu_entries[MENU_ENTRY_SHA1].visibility = VISIBILITY_INVISIBLE; + } + + if(file_entry->is_folder) { + menu_entries[MENU_ENTRY_SHA1].visibility = VISIBILITY_INVISIBLE; } if(file_entry->type != FILE_TYPE_VPK) { @@ -790,6 +798,14 @@ void contextMenuCtrl() { dialog_step = DIALOG_STEP_NEW_FOLDER; break; } + + case MENU_ENTRY_SHA1: + { + // Ensure user wants to actually take the hash + initMessageDialog(SCE_MSG_DIALOG_BUTTON_TYPE_YESNO, language_container[HASH_FILE_QUESTION]); + dialog_step = DIALOG_STEP_HASH_QUESTION; + break; + } case MENU_ENTRY_INSTALL_ALL: { @@ -982,7 +998,48 @@ int dialogSteps() { } break; + + case DIALOG_STEP_HASH_QUESTION: + if (msg_result == MESSAGE_DIALOG_RESULT_YES) { + // Throw up the progress bar, enter hashing state + initMessageDialog(MESSAGE_DIALOG_PROGRESS_BAR, language_container[HASHING]); + dialog_step = DIALOG_STEP_HASH_CONFIRMED; + } else if (msg_result == MESSAGE_DIALOG_RESULT_NO) { + // Quit + dialog_step = DIALOG_STEP_NONE; + } + + break; + + case DIALOG_STEP_HASH_CONFIRMED: + if (msg_result == MESSAGE_DIALOG_RESULT_RUNNING) { + // User has confirmed desire to hash, get requested file entry + FileListEntry *file_entry = fileListGetNthEntry(&file_list, base_pos + rel_pos); + + // Place the full file path in cur_file + snprintf(cur_file, MAX_PATH_LENGTH, "%s%s", file_list.path, file_entry->name); + + HashArguments args; + args.file_path = cur_file; + + // Create a thread to run out actual sum + SceUID thid = sceKernelCreateThread("hash_thread", (SceKernelThreadEntry)hash_thread, 0x40, 0x10000, 0, 0, NULL); + if (thid >= 0) + sceKernelStartThread(thid, sizeof(HashArguments), &args); + dialog_step = DIALOG_STEP_HASHING; + } + + break; + + case DIALOG_STEP_HASH_DISPLAY: + // Reset dialog state when user selects yes/no + if (msg_result == MESSAGE_DIALOG_RESULT_NONE || msg_result == MESSAGE_DIALOG_RESULT_FINISHED) { + dialog_step = DIALOG_STEP_NONE; + } + + break; + case DIALOG_STEP_INSTALL_QUESTION: if (msg_result == MESSAGE_DIALOG_RESULT_YES) { initMessageDialog(MESSAGE_DIALOG_PROGRESS_BAR, language_container[INSTALLING]); diff --git a/main.h b/main.h index 5aff1ee..9458c7b 100644 --- a/main.h +++ b/main.h @@ -179,6 +179,11 @@ enum DialogSteps { DIALOG_STEP_DOWNLOADED, DIALOG_STEP_EXTRACTING, DIALOG_STEP_EXTRACTED, + + DIALOG_STEP_HASH_QUESTION, + DIALOG_STEP_HASH_CONFIRMED, + DIALOG_STEP_HASHING, + DIALOG_STEP_HASH_DISPLAY, }; extern vita2d_pgf *font; diff --git a/resources/english_us.txt b/resources/english_us.txt index 3732502..ac836cb 100644 --- a/resources/english_us.txt +++ b/resources/english_us.txt @@ -15,6 +15,9 @@ DELETE = "Delete" RENAME = "Rename" NEW_FOLDER = "New folder" FOLDER = "Folder" +SHA1 = "SHA1 checksum" +HASHING = "Hashing..." +HASH_FILE_QUESTION = "SHA1 hashing may take a long time. Continue?" COPIED_FILE = "Copied %d file." COPIED_FOLDER = "Copied %d folder." COPIED_FILES_FOLDERS = "Copied %d files/folders."