All formats

UnRAR library

LanguageC++ (C API in dll.hpp)
LicenseUnRAR license (freeware, source available, not OSI-approved: free to use in any software, but the code can't be used to create a RAR compressor)
Repositorysource tarball unrarsrc-*.tar.gz from rarlab.com/rar_add.htm
Documentationunrardll.txt in the UnRAR.dll package on rarlab.com/rar_add.htm
Depends onnothing
Read / writeyes / no (all RAR versions: RAR 1.5-4, RAR5, RAR 7 large dictionaries; encrypted and multi-volume archives)

UnRAR is the decompression code from the makers of WinRAR. It’s the reference implementation and supports every RAR feature: RAR4 and RAR5, solid archives, password-protected files and headers, multi-volume sets and recovery records. SumatraPDF, rarfile (through the unrar tool) and many comic readers use it.

The license is not open source in the OSI sense. You may use and redistribute UnRAR in free and commercial programs, but you must not use it to re-create the RAR compression algorithm. If you need an OSI license, use libarchive, which has its own RAR decoder.

The library API (dll.hpp) is sequential: read a header, then extract, test or skip that file, repeat. Pages arrive in archive order; to read a file into memory, “test” it and collect the data in a callback.

Installation#

Shell
# Debian / Ubuntu (non-free)
sudo apt install libunrar-dev
# from source: builds libunrar.so and libunrar.a
tar xzf unrarsrc-*.tar.gz && cd unrar && make lib

On Windows, download UnRARDLL.exe from RARLAB (unrar.dll, unrar64.dll, headers and documentation) or build UnRARDll.vcxproj from the source. On Unix, compile your code with -D_UNIX (dll.hpp needs it) and link with -lunrar.

Basic usage: list pages in natural order#

C++
#ifdef _WIN32
#include <windows.h>
#endif
#include "dll.hpp"   // from the UnRAR source; Debian installs it as <unrar/dll.hpp>
#include <algorithm>
#include <cctype>
#include <clocale>
#include <cstdio>
#include <string>
#include <vector>

// naturalLess() and isPage() as on the libarchive page: natural, case-insensitive
// order and an image-extension filter that skips __MACOSX and dotfiles.
bool naturalLess(const std::string& a, const std::string& b);
bool isPage(const std::string& name);

std::vector<std::string> listPages(const char* path) {
    std::vector<std::string> pages;
    RAROpenArchiveDataEx open = {};
    open.ArcName = (char*)path;
    open.OpenMode = RAR_OM_LIST;
    HANDLE h = RAROpenArchiveEx(&open);
    if (!h || open.OpenResult != ERAR_SUCCESS) {
        fprintf(stderr, "can't open %s: error %u\n", path, open.OpenResult);
        return pages;
    }
    RARHeaderDataEx hd = {};
    while (RARReadHeaderEx(h, &hd) == ERAR_SUCCESS) {
        if (!(hd.Flags & RHDF_DIRECTORY) && isPage(hd.FileName))
            pages.push_back(hd.FileName);
        RARProcessFile(h, RAR_SKIP, nullptr, nullptr);
    }
    RARCloseArchive(h);
    std::sort(pages.begin(), pages.end(), naturalLess);
    return pages;
}

int main(int argc, char** argv) {
    setlocale(LC_ALL, "");  // FileName is converted to the current locale (UTF-8 on Linux)
    std::vector<std::string> pages = listPages(argv[1]);
    for (size_t i = 0; i < pages.size(); i++)
        printf("%3zu %s\n", i + 1, pages[i].c_str());
    return 0;
}

FileName is in the current locale’s encoding; on Windows use the FileNameW (UTF-16) field and ArcNameW for Unicode paths.

How to read a page into memory#

Open in extract mode, find the header, and call RARProcessFile with RAR_TEST: UnRAR decompresses the file and passes the data to your callback with UCM_PROCESSDATA instead of writing it to disk.

C++
static int CALLBACK collect(UINT msg, LPARAM userData, LPARAM p1, LPARAM p2) {
    if (msg == UCM_PROCESSDATA) {
        auto* out = (std::vector<unsigned char>*)userData;
        auto* data = (unsigned char*)p1;
        out->insert(out->end(), data, data + p2);
    }
    return 1;  // continue; return -1 to abort
}

std::vector<unsigned char> readEntry(const char* path, const std::string& name) {
    std::vector<unsigned char> data;
    RAROpenArchiveDataEx open = {};
    open.ArcName = (char*)path;
    open.OpenMode = RAR_OM_EXTRACT;
    open.Callback = collect;
    open.UserData = (LPARAM)&data;
    HANDLE h = RAROpenArchiveEx(&open);
    if (!h || open.OpenResult != ERAR_SUCCESS) return data;
    RARHeaderDataEx hd = {};
    while (RARReadHeaderEx(h, &hd) == ERAR_SUCCESS) {
        if (name == hd.FileName) {
            if (RARProcessFile(h, RAR_TEST, nullptr, nullptr) != ERAR_SUCCESS) data.clear();
            break;
        }
        // in a solid archive, skipping still decompresses the file internally
        RARProcessFile(h, RAR_SKIP, nullptr, nullptr);
    }
    RARCloseArchive(h);
    return data;
}

How to extract the cover#

C++
std::vector<std::string> pages = listPages("comic.cbr");
if (!pages.empty()) {
    std::vector<unsigned char> cover = readEntry("comic.cbr", pages[0]);
    std::string ext = pages[0].substr(pages[0].find_last_of('.'));
    if (FILE* f = fopen(("cover" + ext).c_str(), "wb")) {
        fwrite(cover.data(), 1, cover.size(), f);
        fclose(f);
    }
}

To extract a file to disk instead, call RARProcessFile(h, RAR_EXTRACT, nullptr, (char*)"cover.jpg").

How to read ComicInfo.xml#

Find the name case-insensitively, read it with readEntry and parse it with an XML library (here pugixml):

C++
#include <pugixml.hpp>
#include <cstring>

void printComicInfo(const char* path) {
    std::string infoName;
    RAROpenArchiveDataEx open = {};
    open.ArcName = (char*)path;
    open.OpenMode = RAR_OM_LIST;
    if (HANDLE h = RAROpenArchiveEx(&open)) {
        RARHeaderDataEx hd = {};
        while (RARReadHeaderEx(h, &hd) == ERAR_SUCCESS) {
            std::string n = hd.FileName;
            for (auto& c : n) c = (char)tolower((unsigned char)c);
            if (n == "comicinfo.xml") infoName = hd.FileName;
            RARProcessFile(h, RAR_SKIP, nullptr, nullptr);
        }
        RARCloseArchive(h);
    }
    if (infoName.empty()) return;

    std::vector<unsigned char> xml = readEntry(path, infoName);
    pugi::xml_document doc;
    if (!doc.load_buffer(xml.data(), xml.size())) return;
    pugi::xml_node ci = doc.child("ComicInfo");
    printf("%s #%s: %s\n", ci.child_value("Series"), ci.child_value("Number"), ci.child_value("Title"));
    for (pugi::xml_node p : ci.child("Pages").children("Page"))
        if (strstr(p.attribute("Type").value(), "FrontCover"))
            printf("cover is page index %d\n", p.attribute("Image").as_int());  // index in sorted order
}

How to open a password-protected CBR#

Set the password after opening, before reading headers (needed when the headers are encrypted too):

C++
HANDLE h = RAROpenArchiveEx(&open);
RARSetPassword(h, (char*)"secret");

Or handle UCM_NEEDPASSWORDW in the callback to ask the user only when needed.

Tips#