/* patch_vorbis.c - retarget a DLL's KERNEL32 import to the XP shim.
 *
 * Reads  C:\WINDOWS\system32\vorbis.acm  (or a path given on the command line)
 * Writes vorbis_xp.acm next to it, with:
 *
 *   1. the import descriptor's "KERNEL32.dll" name string rewritten to
 *      "KERNXP32.dll"  (same length - 12 chars - so nothing else shifts)
 *   2. MajorSubsystemVersion/MinorSubsystemVersion lowered to 5.02
 *   3. the Bound Import directory cleared, so the loader cannot use a
 *      stale binding that still points at the real kernel32
 *
 * The original file is never modified.
 */

#define _WIN32_WINNT 0x0502
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static BYTE *g_buf;
static DWORD g_size;
static IMAGE_SECTION_HEADER *g_sec;
static WORD g_nsec;

static DWORD rva2off(DWORD rva)
{
    WORD i;
    for (i = 0; i < g_nsec; i++) {
        DWORD va = g_sec[i].VirtualAddress;
        DWORD sz = g_sec[i].Misc.VirtualSize;
        if (sz < g_sec[i].SizeOfRawData)
            sz = g_sec[i].SizeOfRawData;
        if (rva >= va && rva < va + sz)
            return rva - va + g_sec[i].PointerToRawData;
    }
    return 0;
}

int main(int argc, char **argv)
{
    const char *inpath  = (argc > 1) ? argv[1] : "C:\\WINDOWS\\system32\\vorbis.acm";
    const char *outpath = (argc > 2) ? argv[2] : "vorbis_xp.acm";
    HANDLE hf;
    DWORD got = 0;
    IMAGE_DOS_HEADER *dos;
    IMAGE_NT_HEADERS64 *nt;
    IMAGE_DATA_DIRECTORY *impdir, *bnddir;
    IMAGE_IMPORT_DESCRIPTOR *imp;
    int patched = 0;

    printf("input : %s\n", inpath);
    printf("output: %s\n\n", outpath);

    hf = CreateFileA(inpath, GENERIC_READ, FILE_SHARE_READ, NULL,
                     OPEN_EXISTING, 0, NULL);
    if (hf == INVALID_HANDLE_VALUE) {
        printf("ERROR: cannot open input, GetLastError=%lu\n",
               (unsigned long)GetLastError());
        return 1;
    }
    g_size = GetFileSize(hf, NULL);
    g_buf = (BYTE *)malloc(g_size);
    if (!g_buf) { printf("ERROR: out of memory\n"); return 1; }
    if (!ReadFile(hf, g_buf, g_size, &got, NULL) || got != g_size) {
        printf("ERROR: read failed\n"); return 1;
    }
    CloseHandle(hf);
    printf("read %lu bytes\n", (unsigned long)g_size);

    dos = (IMAGE_DOS_HEADER *)g_buf;
    if (dos->e_magic != IMAGE_DOS_SIGNATURE) {
        printf("ERROR: not a PE file\n"); return 1;
    }
    nt = (IMAGE_NT_HEADERS64 *)(g_buf + dos->e_lfanew);
    if (nt->Signature != IMAGE_NT_SIGNATURE) {
        printf("ERROR: bad PE signature\n"); return 1;
    }
    if (nt->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
        printf("ERROR: this is not a 64-bit DLL\n"); return 1;
    }

    g_nsec = nt->FileHeader.NumberOfSections;
    g_sec  = IMAGE_FIRST_SECTION(nt);

    /* --- 1. rewrite the import DLL name ------------------------------- */
    impdir = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    if (!impdir->VirtualAddress) { printf("ERROR: no import table\n"); return 1; }

    imp = (IMAGE_IMPORT_DESCRIPTOR *)(g_buf + rva2off(impdir->VirtualAddress));
    for (; imp->Name; imp++) {
        DWORD off = rva2off(imp->Name);
        char *name = (char *)(g_buf + off);
        if (_stricmp(name, "KERNEL32.dll") == 0) {
            printf("found KERNEL32.dll import name at file offset 0x%lX\n",
                   (unsigned long)off);
            memcpy(name, "KERNXP32.dll", 12);
            printf("  -> rewritten to KERNXP32.dll\n");
            patched = 1;
        }
    }
    if (!patched) {
        printf("ERROR: no KERNEL32.dll import descriptor found\n");
        return 1;
    }

    /* --- 2. lower the required subsystem version ---------------------- */
    printf("subsystem version %u.%02u",
           (unsigned)nt->OptionalHeader.MajorSubsystemVersion,
           (unsigned)nt->OptionalHeader.MinorSubsystemVersion);
    nt->OptionalHeader.MajorSubsystemVersion = 5;
    nt->OptionalHeader.MinorSubsystemVersion = 2;
    nt->OptionalHeader.MajorOperatingSystemVersion = 5;
    nt->OptionalHeader.MinorOperatingSystemVersion = 2;
    printf(" -> 5.02\n");

    /* --- 3. clear any bound-import directory -------------------------- */
    bnddir = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT];
    if (bnddir->VirtualAddress || bnddir->Size) {
        printf("clearing bound import directory\n");
        bnddir->VirtualAddress = 0;
        bnddir->Size = 0;
    }

    /* PE checksum is not verified for user-mode DLLs; zero it so no
     * tool reports a stale value. */
    nt->OptionalHeader.CheckSum = 0;

    /* --- write it out -------------------------------------------------- */
    hf = CreateFileA(outpath, GENERIC_WRITE, 0, NULL,
                     CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hf == INVALID_HANDLE_VALUE) {
        printf("ERROR: cannot create output, GetLastError=%lu\n",
               (unsigned long)GetLastError());
        return 1;
    }
    if (!WriteFile(hf, g_buf, g_size, &got, NULL) || got != g_size) {
        printf("ERROR: write failed\n"); return 1;
    }
    CloseHandle(hf);

    printf("\nwrote %lu bytes to %s\n", (unsigned long)got, outpath);
    printf("\nNext steps:\n");
    printf("  1. copy KERNXP32.dll     -> C:\\WINDOWS\\system32\\\n");
    printf("  2. back up the original  -> copy C:\\WINDOWS\\system32\\vorbis.acm vorbis.acm.bak\n");
    printf("  3. copy %s -> C:\\WINDOWS\\system32\\vorbis.acm\n", outpath);
    printf("  4. run acmtest64.exe - the Ogg Vorbis driver should now appear\n");
    return 0;
}
