cl_error_t cli_egg_extract_file(void* hArchive, const char** filename, const char** output_buffer, size_t* output_buffer_length)
{
    cl_error_t status          = CL_EPARSE;
    egg_handle* handle         = NULL;
    egg_file* currFile         = NULL;
    char* decompressed         = NULL;
    uint64_t decompressed_size = 0;
    uint64_t i                 = 0;

    if (!hArchive || !filename || !output_buffer || !output_buffer_length) {
        cli_errmsg("cli_egg_extract_file: Invalid args!\n");
        status = CL_EARG;
        goto done;
    }

    *output_buffer        = NULL;
    *output_buffer_length = 0;

    handle = (egg_handle*)hArchive;
    if (CL_SUCCESS != EGG_VALIDATE_HANDLE(handle)) {
        cli_errmsg("cli_egg_extract_file: Invalid handle values!\n");
        status = CL_EARG;
        goto done;
    }

    if (handle->fileExtractionIndex >= handle->nFiles) {
        cli_errmsg("cli_egg_extract_file: File index exceeds number of files in archive!\n");
        goto done;
    }

    currFile = handle->files[handle->fileExtractionIndex];
    if (NULL == currFile) {
        cli_errmsg("cli_egg_extract_file: invalid egg_file pointer!\n");
        goto done;
    }

    if (NULL == currFile->file) {
        cli_errmsg("cli_egg_extract_file: egg_file is missing file header!\n");
        goto done;
    }

    if (NULL == currFile->filename.name_utf8) {
        cli_errmsg("cli_egg_extract_file: egg_file is missing filename!\n");
        goto done;
    }

    if (handle->bSolid) {
        /*
         * TODO: Add support for extracting files from solid archives.
         *
         * For solid archives, the blocks are shared between all of the files.
         * To unpack them, we'd have to identify which block(s) each file would
         * be associated with.
         *
         * Then in theory a single file could be extracted without decompressing
         * all of the blocks at the same time.
         *
         * To be efficient about it, a block could have some sort of ref count
         * or list of associated files. Then during extraction, the decompressed
         * data for each block that is shared between files is not freed until
         * all of the files associated with that block have been extracted.
         */
    } else {
        if (currFile->nBlocks == 0 || currFile->blocks == NULL) {
            cli_dbgmsg("cli_egg_extract_file: Empty file!\n");
        }

        for (i = 0; i < currFile->nBlocks; i++) {
            char* decompressed_tmp;
            egg_block* currBlock = currFile->blocks[i];
            cl_error_t retval    = CL_EPARSE;

            if (NULL == currBlock->blockHeader) {
                cli_errmsg("cli_egg_extract_file: current egg_block missing header!\n");
                break;
            }
            switch (currBlock->blockHeader->compress_algorithm) {
                case BLOCK_HEADER_COMPRESS_ALGORITHM_STORE: {
                    /*
                     * No compression. Woohoo!
                     */
                    if (currBlock->blockHeader->compress_size == 0) {
                        cli_warnmsg("cli_egg_extract_file: blockHeader compress_size is 0!\n");
                        break;
                    } else if (currBlock->blockHeader->compress_size != currBlock->blockHeader->uncompress_size) {
                        cli_warnmsg("cli_egg_extract_file: blockHeader compress_size != uncompress_size!\n");
                        break;
                    }
                    decompressed_tmp = cli_realloc(decompressed, (size_t)decompressed_size + currBlock->blockHeader->compress_size);
                    if (NULL == decompressed_tmp) {
                        cli_errmsg("cli_egg_extract_file: Failed to allocate %" PRIu64 " bytes for decompressed file!\n",
                                   decompressed_size);
                        status = CL_EMEM;
                        goto done;
                    }
                    decompressed = decompressed_tmp;

                    memcpy(decompressed + decompressed_size, currBlock->compressedData, currBlock->blockHeader->compress_size);
                    decompressed_size += currBlock->blockHeader->compress_size;

                    retval = CL_SUCCESS;
                    break;
                }
                case BLOCK_HEADER_COMPRESS_ALGORITHM_DEFLATE: {
                    char* decompressed_block       = NULL;
                    size_t decompressed_block_size = 0;

                    if (CL_SUCCESS != cli_egg_deflate_decompress(currBlock->compressedData,
                                                                 currBlock->blockHeader->compress_size,
                                                                 &decompressed_block,
                                                                 &decompressed_block_size)) {
                        /* Failed to decompress block */
                        cli_warnmsg("Failed to decompress RFC 1951 deflate compressed block\n");
                        goto done;
                    }
                    /* Decompressed block. Add it to the file data */
                    decompressed_tmp = cli_realloc(decompressed, (size_t)decompressed_size + decompressed_block_size);
                    if (NULL == decompressed_tmp) {
                        cli_errmsg("cli_egg_extract_file: Failed to allocate %" PRIu64 " bytes for decompressed file!\n",
                                   decompressed_size);
                        free(decompressed_block);
                        status = CL_EMEM;
                        goto done;
                    }
                    decompressed = decompressed_tmp;

                    memcpy(decompressed + decompressed_size, decompressed_block, decompressed_block_size);
                    decompressed_size += decompressed_block_size;

                    free(decompressed_block);

                    retval = CL_SUCCESS;
                    break;
                }
                case BLOCK_HEADER_COMPRESS_ALGORITHM_BZIP2: {
#if HAVE_BZLIB_H
                    char* decompressed_block       = NULL;
                    size_t decompressed_block_size = 0;

                    if (CL_SUCCESS != cli_egg_bzip2_decompress(currBlock->compressedData,
                                                               currBlock->blockHeader->compress_size,
                                                               &decompressed_block,
                                                               &decompressed_block_size)) {
                        /* Failed to decompress block */
                        cli_warnmsg("Failed to decompress BZIP2 compressed block\n");
                        goto done;
                    }
                    /* Decompressed block. Add it to the file data */
                    decompressed_tmp = cli_realloc(decompressed, (size_t)decompressed_size + decompressed_block_size);
                    if (NULL == decompressed_tmp) {
                        cli_errmsg("cli_egg_extract_file: Failed to allocate %" PRIu64 " bytes for decompressed file!\n",
                                   decompressed_size);
                        free(decompressed_block);
                        status = CL_EMEM;
                        goto done;
                    }
                    decompressed = decompressed_tmp;

                    memcpy(decompressed + decompressed_size, decompressed_block, decompressed_block_size);
                    decompressed_size += decompressed_block_size;

                    free(decompressed_block);

                    retval = CL_SUCCESS;
                    break;
#else
                    cli_warnmsg("cli_egg_extract_file: BZIP2 decompression support not available.\n");
                    goto done;
#endif
                }
                case BLOCK_HEADER_COMPRESS_ALGORITHM_AZO: {
                    cli_warnmsg("cli_egg_extract_file: AZO decompression not yet supported.\n");
                    goto done;
                    //break;
                }
                case BLOCK_HEADER_COMPRESS_ALGORITHM_LZMA: {
                    cli_warnmsg("cli_egg_extract_file: LZMA decompression not yet supported.\n");
                    goto done;
                    // char* decompressed_block       = NULL;
                    // size_t decompressed_block_size = 0;

                    // if (CL_SUCCESS != cli_egg_lzma_decompress(currBlock->compressedData,
                    //                                       currBlock->blockHeader->compress_size,
                    //                                       &decompressed_block,
                    //                                       &decompressed_block_size)) {
                    //     /* Failed to decompress block */
                    //     cli_warnmsg("Failed to decompress LZMA compressed block\n");
                    //     goto done;
                    // }
                    // /* Decompressed block. Add it to the file data */
                    // decompressed_tmp = cli_realloc(decompressed, (size_t)decompressed_size + decompressed_block_size);
                    // if (NULL == decompressed_tmp) {
                    //     cli_errmsg("cli_egg_extract_file: Failed to allocate %" PRIu64 " bytes for decompressed file!\n",
                    //                decompressed_size);
                    //     free(decompressed_block);
                    //     status = CL_EMEM;
                    //     goto done;
                    // }
                    // decompressed = decompressed_tmp;

                    // memcpy(decompressed + decompressed_size, decompressed_block, decompressed_block_size);
                    // decompressed_size += decompressed_block_size;

                    // free(decompressed_block);

                    // retval = CL_SUCCESS;
                    // break;
                }
                default: {
                    cli_errmsg("cli_egg_extract_file: unknown compression algorithm: %d!\n",
                               currBlock->blockHeader->compress_algorithm);
                    goto done;
                }
            }

            if (CL_SUCCESS != retval) {
                cli_warnmsg("cli_egg_extract_file: Unable to decompress file: %s\n",
                            currFile->filename.name_utf8);
            }

            if ((i == currFile->nBlocks - 1) &&                       // last block ?
                (decompressed_size != currFile->file->file_length)) { // right amount of data ?
                cli_warnmsg("cli_egg_extract_file: alleged filesize (%" PRIu64 ") != actual filesize (%" PRIu64 ")!\n",
                            currFile->file->file_length,
                            decompressed_size);
            }
        }
    }

    cli_dbgmsg("cli_egg_extract_file: File extracted: %s\n", currFile->filename.name_utf8);
    *filename             = strdup(currFile->filename.name_utf8);
    *output_buffer        = decompressed;
    *output_buffer_length = decompressed_size;
    status                = CL_SUCCESS;

done:
    handle->fileExtractionIndex += 1;

    if (CL_SUCCESS != status) {
        /* Free buffer */
        if (NULL != decompressed) {
            free(decompressed);
        }
    }

    return status;
}