/* Bare copy of a part of Win32.xs */
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0500
#include <windows.h>

#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#define GETPROC(fn) pfn##fn = (PFN##fn)GetProcAddress(module, #fn)

typedef LONG (WINAPI *PFNRegGetValueA)(HKEY, LPCSTR, LPCSTR, DWORD, LPDWORD, PVOID, LPDWORD);

/* Use explicit struct definition because wSuiteMask and
 * wProductType are not defined in the VC++ 6.0 headers.
 * WORD type has been replaced by unsigned short because
 * WORD is already used by Perl itself.
 */
struct g_osver_t {
    DWORD dwOSVersionInfoSize;
    DWORD dwMajorVersion;
    DWORD dwMinorVersion;
    DWORD dwBuildNumber;
    DWORD dwPlatformId;
    CHAR  szCSDVersion[128];
    unsigned short wServicePackMajor;
    unsigned short wServicePackMinor;
    unsigned short wSuiteMask;
    BYTE  wProductType;
    BYTE  wReserved;
} g_osver = {0, 0, 0, 0, 0, "", 0, 0, 0, 0, 0};
BOOL g_osver_ex = TRUE;

XS(w32_GetOSVersion)
{
    dXSARGS;
    if (items)
	Perl_croak(aTHX_ "usage: Win32::GetOSVersion()");

    if (GIMME_V == G_SCALAR) {
        XSRETURN_IV(g_osver.dwPlatformId);
    }
    XPUSHs(sv_2mortal(newSVpvn(g_osver.szCSDVersion, strlen(g_osver.szCSDVersion))));

    XPUSHs(sv_2mortal(newSViv(g_osver.dwMajorVersion)));
    XPUSHs(sv_2mortal(newSViv(g_osver.dwMinorVersion)));
    XPUSHs(sv_2mortal(newSViv(g_osver.dwBuildNumber)));
    XPUSHs(sv_2mortal(newSViv(g_osver.dwPlatformId)));
    if (g_osver_ex) {
        XPUSHs(sv_2mortal(newSViv(g_osver.wServicePackMajor)));
        XPUSHs(sv_2mortal(newSViv(g_osver.wServicePackMinor)));
        XPUSHs(sv_2mortal(newSViv(g_osver.wSuiteMask)));
        XPUSHs(sv_2mortal(newSViv(g_osver.wProductType)));
    }
    PUTBACK;
}

XS(w32_GetProcessPrivileges)
{
    dXSARGS;
    BOOL ret;
    HV *priv_hv;
    HANDLE proc_handle, token;
    char *priv_name = NULL;
    TOKEN_PRIVILEGES *privs = NULL;
    DWORD i, pid, priv_name_len = 100, privs_len = 300;

    if (items > 1)
        Perl_croak(aTHX_ "usage: Win32::GetProcessPrivileges([$pid])");

    if (items == 0) {
        EXTEND(SP, 1);
        pid = GetCurrentProcessId();
    }
    else {
        pid = (DWORD)SvUV(ST(0));
    }

    proc_handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);

    if (!proc_handle)
        XSRETURN_NO;

    ret = OpenProcessToken(proc_handle, TOKEN_QUERY, &token);
    CloseHandle(proc_handle);

    if (!ret)
        XSRETURN_NO;

    do {
        Renewc(privs, privs_len, char, TOKEN_PRIVILEGES);
        ret = GetTokenInformation(
            token, TokenPrivileges, privs, privs_len, &privs_len
        );
    } while (!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER);

    CloseHandle(token);

    if (!ret) {
        Safefree(privs);
        XSRETURN_NO;
    }

    priv_hv = newHV();
    New(0, priv_name, priv_name_len, char);

    for (i = 0; i < privs->PrivilegeCount; ++i) {
        DWORD ret_len = 0;
        LUID_AND_ATTRIBUTES *priv = &privs->Privileges[i];
        BOOL is_enabled = !!(priv->Attributes & SE_PRIVILEGE_ENABLED);

        if (priv->Attributes & SE_PRIVILEGE_REMOVED)
            continue;

        do {
            ret_len = priv_name_len;
            ret = LookupPrivilegeNameA(
                NULL, &priv->Luid, priv_name, &ret_len
            );

            if (ret_len > priv_name_len) {
                priv_name_len = ret_len + 1;
                Renew(priv_name, priv_name_len, char);
            }
        } while (!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER);

        if (!ret) {
            SvREFCNT_dec((SV*)priv_hv);
            Safefree(privs);
            Safefree(priv_name);
            XSRETURN_NO;
        }

        hv_store(priv_hv, priv_name, ret_len, newSViv(is_enabled), 0);
    }

    Safefree(privs);
    Safefree(priv_name);

    ST(0) = sv_2mortal(newRV_noinc((SV*)priv_hv));
    XSRETURN(1);
}

XS(w32_IsDeveloperModeEnabled)
{
    dXSARGS;
    LONG status;
    DWORD val, val_size = sizeof(val);
    PFNRegGetValueA pfnRegGetValueA;
    HMODULE module;

    if (items)
        Perl_croak(aTHX_ "usage: Win32::IsDeveloperModeEnabled()");

    EXTEND(SP, 1);

    /* developer mode was introduced in Windows 10 */
    if (g_osver.dwMajorVersion < 10)
        XSRETURN_NO;

    module = GetModuleHandleA("advapi32.dll");
    GETPROC(RegGetValueA);
    if (!pfnRegGetValueA)
        XSRETURN_NO;

    status = pfnRegGetValueA(
        HKEY_LOCAL_MACHINE,
        "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock",
        "AllowDevelopmentWithoutDevLicense",
        RRF_RT_REG_DWORD | KEY_WOW64_64KEY,
        NULL,
        &val,
        &val_size
    );

    if (status == ERROR_SUCCESS && val == 1)
        XSRETURN_YES;

    XSRETURN_NO;
}

MODULE = Test::File            PACKAGE = Test::File::Win32

PROTOTYPES: DISABLE

BOOT:
{
    const char *file = __FILE__;

    if (g_osver.dwOSVersionInfoSize == 0) {
        g_osver.dwOSVersionInfoSize = sizeof(g_osver);
        if (!GetVersionExA((OSVERSIONINFOA*)&g_osver)) {
            g_osver_ex = FALSE;
            g_osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
            GetVersionExA((OSVERSIONINFOA*)&g_osver);
        }
    }

    newXS("Test::File::Win32::GetOSVersion", w32_GetOSVersion, file);
    newXS("Test::File::Win32::GetProcessPrivileges", w32_GetProcessPrivileges, file);
    newXS("Test::File::Win32::IsDeveloperModeEnabled", w32_IsDeveloperModeEnabled, file);
    XSRETURN_YES;
}