/* Standalone host for compatible FreeArc/CLS-SREP repack decoders.
 * Build: i686-w64-mingw32-gcc -O2 -Wall -mwindows -o unarc-extract.exe unarc-extract.c
 * Arguments are absolute Windows paths: unarc.dll archive output-directory temp-directory.
 */
#include <windows.h>
#include <stdio.h>
#include <string.h>

typedef int (__stdcall *callback_t)(char *, int, int, char *);
typedef int (__cdecl *extract_t)(callback_t, ...);

static int __stdcall progress(char *kind, int first, int second, char *message)
{
    static int read_bucket = -1, write_bucket = -1;
    if (kind && (!strcmp(kind, "read") || !strcmp(kind, "write"))) {
        int *bucket = !strcmp(kind, "read") ? &read_bucket : &write_bucket;
        if (first / 256 == *bucket) return 0;
        *bucket = first / 256;
    }
    fprintf(stderr, "%s %d %d", kind ? kind : "callback", first, second);
    if (message && kind && (!strcmp(kind, "error") || !strcmp(kind, "filename")))
        fprintf(stderr, " %s", message);
    fputc('\n', stderr);
    fflush(stderr);
    return 0;
}

int main(int argc, char **argv)
{
    char directory[MAX_PATH], output[MAX_PATH + 4], temporary[MAX_PATH + 4];
    if (argc != 5 || strlen(argv[1]) >= sizeof(directory) ||
        strlen(argv[3]) >= MAX_PATH || strlen(argv[4]) >= MAX_PATH) {
        fprintf(stderr, "Usage: unarc-extract.exe DLL ARCHIVE OUTPUT TEMP (absolute Windows paths)\n");
        return 2;
    }
    strcpy(directory, argv[1]);
    char *separator = strrchr(directory, '\\');
    if (!separator) return 2;
    *separator = 0;
    if (!SetCurrentDirectoryA(directory) || !SetDllDirectoryA(directory)) {
        fprintf(stderr, "Cannot select decoder directory: %lu\n", GetLastError());
        return 2;
    }
    if (!SetEnvironmentVariableA("TEMP", argv[4]) || !SetEnvironmentVariableA("TMP", argv[4]))
        return 2;

    HANDLE mapping = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
        0, 256, "Init_MapFile_");
    DWORD creation_status = GetLastError();
    if (!mapping) {
        fprintf(stderr, "Cannot create initialization mapping: %lu\n", creation_status);
        return 2;
    }
    char *prefix = MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 256);
    if (!prefix) {
        fprintf(stderr, "Cannot map initialization data: %lu\n", GetLastError());
        CloseHandle(mapping);
        return 2;
    }
    if (creation_status == ERROR_ALREADY_EXISTS && memcmp(prefix, "Global\\", 8)) {
        fprintf(stderr, "An incompatible Init_MapFile_ already exists; use a separate Wine prefix.\n");
        UnmapViewOfFile(prefix);
        CloseHandle(mapping);
        return 2;
    }
    if (creation_status != ERROR_ALREADY_EXISTS) memcpy(prefix, "Global\\", 8);

    HMODULE library = LoadLibraryA(argv[1]);
    FARPROC address = library ? GetProcAddress(library, "FreeArcExtract") : NULL;
    extract_t extract = NULL;
    _Static_assert(sizeof(address) == sizeof(extract), "Function pointer sizes must match");
    memcpy(&extract, &address, sizeof(extract));
    int status = 2;
    if (!extract) {
        fprintf(stderr, "Cannot load FreeArcExtract: %lu\n", GetLastError());
    } else {
        snprintf(output, sizeof(output), "-dp%s", argv[3]);
        snprintf(temporary, sizeof(temporary), "-w%s", argv[4]);
        int result = extract(progress, "x", "-cfgarc.ini", temporary, "-o-", output,
            "--", argv[2], "", NULL);
        fprintf(stderr, "FreeArcExtract returned %d\n", result);
        status = result ? 1 : 0;
    }
    /* Keep the namespace mapping alive until extraction and DLL cleanup finish. */
    if (library) FreeLibrary(library);
    UnmapViewOfFile(prefix);
    CloseHandle(mapping);
    return status;
}
