int
get_menu_selection(char** headers, char** items, int menu_only,
                   int initial_selection) {
    // throw away keys pressed previously, so user doesn't
    // accidentally trigger menu items.
    ui_clear_key_queue();

    int item_count = ui_start_menu(headers, items, initial_selection);
    int selected = initial_selection;
    int chosen_item = -1;

    // Some users with dead enter keys need a way to turn on power to select.
    // Jiggering across the wrapping menu is one "secret" way to enable it.
    // We can't rely on /cache or /sdcard since they may not be available.
    int wrap_count = 0;

    while (chosen_item < 0 && chosen_item != GO_BACK) {
        int key = ui_wait_key();
        int visible = ui_text_visible();

        int action = device_handle_key(key, visible);

        int old_selected = selected;

        if (action < 0) {
            switch (action) {
                case HIGHLIGHT_UP:
                    --selected;
                    selected = ui_menu_select(selected);
                    break;
                case HIGHLIGHT_DOWN:
                    ++selected;
                    selected = ui_menu_select(selected);
                    break;
                case SELECT_ITEM:
                    chosen_item = selected;
                    if (ui_get_showing_back_button()) {
                        if (chosen_item == item_count) {
                            chosen_item = GO_BACK;
                        }
                    }
                    break;
                case NO_ACTION:
                    break;
                case GO_BACK:
                    chosen_item = GO_BACK;
                    break;
            }
        } else if (!menu_only) {
            chosen_item = action;
        }

        if (abs(selected - old_selected) > 1) {
            wrap_count++;
            if (wrap_count == 3) {
                wrap_count = 0;
                if (ui_get_showing_back_button()) {
                    ui_print("Back menu button disabled.\n");
                    ui_set_showing_back_button(0);
                }
                else {
                    ui_print("Back menu button enabled.\n");
                    ui_set_showing_back_button(1);
                }
            }
        }
    }

    ui_end_menu();
    ui_clear_key_queue();
    return chosen_item;
}
int nandroid_restore_md5_check(const char *backup_path, unsigned char flags) {
    DIR *dp;
    FILE *fd;
    int i = 0;
    int j = 0;
    int len = 0;
    int ret = 0;
    int filecount = 0;
    int md5count = 0;
    int use_ui = is_ui_initialized();

    if (empty_nandroid_bitmask(flags)) {
        LOGE("Nothing selected for restore.\n");
        return -1;
    }

    // Dynamically allocated, free them at the end
    char *filenames[MAX_FILES_CHECKED];
    char *filepaths[MAX_FILES_CHECKED];
    char *md5files[MAX_FILES_CHECKED];
    char *md5hashes[MAX_FILES_CHECKED];
    int mf_allocated = 0; // for MissingFiles *mf
    int mm_allocated = 0; // for MissingFiles *mm

    char path[PATH_MAX];
    snprintf(path, PATH_MAX, "%s", backup_path);
    len = strlen(path);
    if (path[len-1] == '/')
        path[len-1] = '\0';

    ui_print("Checking MD5 sums...\n");

    // Read files available in backup path
    dp = opendir(path);
    if (dp != NULL) {
        struct dirent *ep;
        while ((ep = readdir(dp)) && i < MAX_FILES_CHECKED) {
            if (is_selected_for_restore(ep->d_name, flags)) {
                len = strlen(ep->d_name);
                filenames[i] = malloc(sizeof(char[len+1]));
                snprintf(filenames[i], len+1, "%s", ep->d_name);

                len += 1 + strlen(path);
                filepaths[i] = malloc(sizeof(char[len+1]));
                snprintf(filepaths[i], len+1, "%s/%s", path, ep->d_name);
                i++;
            }
        }
        closedir(dp);
        filecount = i;
    }
    if (filecount == 0) {
        ret = -1;
        LOGE("No backup files found in %s\n", path);
        goto out;
    }

    // Read files + hashes available in nandroid.md5
    i = 0;
    char md5path[PATH_MAX];
    snprintf(md5path, PATH_MAX, "%s/%s", path, "nandroid.md5");
    fd = fopen(md5path, "r");
    if (fd != NULL) {
        char tmp[PATH_MAX];
        while (fgets(tmp, PATH_MAX, fd) && i < MAX_FILES_CHECKED) {
            if (tmp[strlen(tmp)-1] == '\n')
                tmp[strlen(tmp)-1] = '\0';
            if (is_selected_for_restore(tmp, flags)) {
                md5hashes[i] = malloc(sizeof(char[HASH_LENGTH+1]));
                snprintf(md5hashes[i], HASH_LENGTH+1, "%s", tmp);

                // md5 hash is followed by two spaces
                len = strlen(tmp) - (HASH_LENGTH+2);
                md5files[i] = malloc(sizeof(char[len+1]));
                snprintf(md5files[i], len+1, "%s", &tmp[HASH_LENGTH+2]);
                i++;
            }
        }
        fclose(fd);
        md5count = i;
    }

#if DEBUG_MD5_CHECKER
    LOGI("[MD5] backup_path: %s\n", path);
    for (i = 0; i < filecount; i++) {
        LOGI("[MD5] files in path: %s\n", filenames[i]);
    }
    for (i = 0; i < md5count; i++) {
        LOGI("[MD5] reference: %s: %s\n", md5files[i], md5hashes[i]);
    }
#endif

    // Cross-reference files in directory to those in nandroid.md5
    // Mark files that are in nandroid.md5 but not in directory (potentially fatal)
    int foundfile;
    int totalmissing = 0;
    MissingFiles *mm = malloc(md5count * sizeof(MissingFiles));
    mm_allocated = 1;
    for (i = 0; i < md5count; i++) {
        foundfile = 0;
        for (j = 0; j < filecount; j++) {
            if (strcmp(md5files[i], filenames[j]) == 0) {
                mm[i] = (MissingFiles){ 0, "" };
                foundfile = 1;
            }
        }
        if (!foundfile) {
            mm[i] = (MissingFiles){ 1, md5files[i] };
            totalmissing++;
        }
    }

    // Warn user of failure for missing files
    if (totalmissing && use_ui) {
        int uiback = ui_is_showing_back_button();
        ui_set_showing_back_button(0);

        const char* headers[totalmissing+8];
        headers[0] = "Backup files are missing:";
        int hi = 1;
        for (i = 0; i < md5count; i++) {
            if (mm[i].is_missing)
                headers[hi++] = mm[i].filename;
        }
        headers[totalmissing+1] = "";
        headers[totalmissing+2] = "Attempting to restore any";
        headers[totalmissing+3] = "of these will fail.";
        headers[totalmissing+4] = "";
        headers[totalmissing+5] = "Continue anyways?";
        headers[totalmissing+6] = "";
        headers[totalmissing+7] = NULL;

        static char* list[] = { "Yes", "No", NULL };

        int chosen_item = get_menu_selection(headers, list, 0, 0);
        if (chosen_item == 0) {
            ui_set_showing_back_button(uiback);
        } else {
            ret = -1;
            ui_set_showing_back_button(uiback);
            LOGE("Aborting\n");
            goto out;
        }
    } else if (totalmissing) {
        // No UI, so no prompt possible; fail the restore
        ret = -1;
        LOGE("Backup files are missing:\n");
        for (i = 0; i < md5count; i++) {
            if (mm[i].is_missing)
                LOGE("%s\n", mm[i].filename);
        }
        LOGE("Aborting\n");
        goto out;
    }

    // Cross-reference files in directory to those in nandroid.md5
    // Mark files that are in directory but not in nandroid.md5 (non-fatal)
    totalmissing = 0;
    MissingFiles *mf = malloc(filecount * sizeof(MissingFiles));
    mf_allocated = 1;
    for (i = 0; i < filecount; i++) {
        foundfile = 0;
        // Ignore files that are not scheduled for restore
        if (!is_selected_for_restore(filenames[i], flags)) {
            mf[i] = (MissingFiles){ 0, "" };
            continue;
        }
        for (j = 0; j < md5count; j++) {
            if (strcmp(filenames[i], md5files[j]) == 0) {
                mf[i] = (MissingFiles){ 0, "" };
                foundfile = 1;
            }
        }
        if (!foundfile) {
            mf[i] = (MissingFiles){ 1, filenames[i] };
            totalmissing++;
        }
    }

    // Warn user about missing md5 references for files
    if (totalmissing && use_ui) {
        int uiback = ui_is_showing_back_button();
        ui_set_showing_back_button(0);

        const char* headers[totalmissing+5];
        headers[0] = "Could not find reference MD5 for:";
        int hi = 1;
        for (i = 0; i < filecount; i++) {
            if (mf[i].is_missing)
                headers[hi++] = mf[i].filename;
        }
        headers[totalmissing+1] = "";
        headers[totalmissing+2] = "Continue anyways?";
        headers[totalmissing+3] = "";
        headers[totalmissing+4] = NULL;

        static char* list[] = { "Yes", "No", NULL };

        int chosen_item = get_menu_selection(headers, list, 0, 0);
        if (chosen_item == 0) {
            ui_set_showing_back_button(uiback);
        } else{
            ret = -1;
            ui_set_showing_back_button(uiback);
            LOGE("Aborting\n");
            goto out;
        }
    } else if (totalmissing) {
        // No UI, so no prompt possible; fail the restore
        ret = -1;
        LOGE("Could not find reference MD5 for:\n");
        for (i = 0; i < filecount; i++) {
            if (mf[i].is_missing)
                LOGE("%s\n", mf[i].filename);
        }
        LOGE("Aborting\n");
        goto out;
    }

    // Compare MD5s of non-missing files that are selected for restore
    int md5matches = 0;
    char md5calc[HASH_LENGTH+1];
    for (i = 0; i < filecount; i++) {
        if (!is_selected_for_restore(filenames[i], flags))
            continue;
        for (j = 0; j < md5count; j++) {
            if (strcmp(filenames[i], md5files[j]) == 0) {
                if (calculate_md5(md5calc, filepaths[i]) != 0) {
                    ret = -1;
                    LOGE("Unable to check MD5 of %s\nAborting\n", filenames[i]);
                    goto out;
                }
                if (strcmp(md5calc, md5hashes[j]) != 0) {
                    ret = -1;
                    LOGE("MD5 mismatch for %s\nAborting\n", filenames[i]);
                    goto out;
                } else {
                    md5matches++;
                }
            }
        }
    }
    if (md5matches) {
        ui_print("All MD5 checksums verified\n");
    } else {
        ui_print("No MD5 verification performed\n");
    }

out:
    if (mf_allocated)
        free(mf);
    if (mm_allocated)
        free(mm);
    for (i = 0; i < filecount; i++) {
        free(filenames[i]);
        free(filepaths[i]);
    }
    for (i = 0; i < md5count; i++) {
        free(md5files[i]);
        free(md5hashes[i]);
    }

    return ret;
}