"""
Clock POSitive Admin keygen
Usage: python keygen.py [MACHINE-ID]

Example: python keygen.py ERI-LAJJS-FAAZI-IEOBO-AQIGD-XGSIZ
"""
import sys
import os
import struct
import subprocess
import tempfile

BPL_DIR = r'c:\ClockBS\ClockPOSitive3 - Copy'
INTERNAL_KEY = 'POSitive ADMIN V3.1x'
APP_NAME = 'Clock POSitive Admin'
PS32 = r'C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe'


# ── Stub PE builder ──────────────────────────────────────────────────────────

def _align(n, a):
    return (n + a - 1) & ~(a - 1)


def _build_stub_pe(dll_name_str, exports):
    """Build a minimal 32-bit PE DLL with no-op (RET) export stubs."""
    IMAGE_BASE = 0x10000000
    SECT_ALIGN = 0x1000
    FILE_ALIGN = 0x200
    n = len(exports)

    # DllMain returns TRUE (stdcall, 3 DWORD params)
    DLLMAIN = bytes([0xB8, 0x01, 0x00, 0x00, 0x00,  # MOV EAX, 1
                     0xC2, 0x0C, 0x00])               # RET 12

    text_rva   = SECT_ALIGN
    text_fo    = FILE_ALIGN
    text_vsz   = len(DLLMAIN) + n
    text_rawsz = FILE_ALIGN

    edata_rva  = 2 * SECT_ALIGN
    edata_fo   = 2 * FILE_ALIGN

    dll_name_b = dll_name_str.encode('ascii') + b'\x00'

    off_dir     = 0
    off_addr    = 40
    off_names   = off_addr  + n * 4
    off_ords    = off_names + n * 4
    off_dllname = off_ords  + n * 2
    off_strs    = off_dllname + len(dll_name_b)

    name_offsets = []
    pos = off_strs
    name_blob = b''
    for nm in exports:
        name_offsets.append(pos)
        name_blob += nm + b'\x00'
        pos += len(nm) + 1

    edata_size   = pos
    edata_rawsz  = _align(edata_size, FILE_ALIGN)

    # Export stubs follow DllMain
    stub_rvas = [text_rva + len(DLLMAIN) + i for i in range(n)]

    # Export directory (40 bytes: 4+4+2+2 + 5×4 + 2×4)
    exp_dir = (struct.pack('<IIHH', 0, 0, 0, 0) +
               struct.pack('<IIIII', edata_rva + off_dllname, 1, n, n,
                           edata_rva + off_addr) +
               struct.pack('<II', edata_rva + off_names,
                           edata_rva + off_ords))

    addr_table  = b''.join(struct.pack('<I', r) for r in stub_rvas)

    sorted_idx  = sorted(range(n), key=lambda i: exports[i])
    name_ptrs   = b''.join(struct.pack('<I', edata_rva + name_offsets[i])
                            for i in sorted_idx)
    ord_table   = b''.join(struct.pack('<H', i) for i in sorted_idx)

    edata = (exp_dir + addr_table + name_ptrs + ord_table +
             dll_name_b + name_blob).ljust(edata_rawsz, b'\x00')

    img_size = _align(edata_rva + edata_size, SECT_ALIGN)

    dos = bytearray(64)
    dos[0:2] = b'MZ'
    struct.pack_into('<I', dos, 60, 64)

    coff = struct.pack('<HHIIIHH', 0x014C, 2, 0, 0, 0, 0xE0, 0x2102)

    opt  = struct.pack('<HBBIIIIIII',
                       0x010B, 0, 0, text_rawsz, edata_rawsz, 0,
                       text_rva, text_rva, edata_rva, IMAGE_BASE)
    opt += struct.pack('<IIHHHHHH',
                       SECT_ALIGN, FILE_ALIGN, 4, 0, 0, 0, 4, 0)
    opt += struct.pack('<IIIIHHIIII',
                       0, img_size, FILE_ALIGN, 0, 3, 0,
                       0x100000, 0x1000, 0x100000, 0x1000)
    opt += struct.pack('<II', 0, 16)

    dirs = bytearray(128)
    struct.pack_into('<II', dirs, 0, edata_rva, edata_size)

    def sec(name, vrva, vsz, rawsz, rawfo, chars):
        return (name.ljust(8, b'\x00')[:8] +
                struct.pack('<IIIIIIHHI', vsz, vrva, rawsz, rawfo,
                            0, 0, 0, 0, chars))

    header = (bytes(dos) + b'PE\x00\x00' + coff + opt + bytes(dirs) +
              sec(b'.text',  text_rva,  text_vsz,  text_rawsz,  text_fo,  0x60000020) +
              sec(b'.edata', edata_rva, edata_size, edata_rawsz, edata_fo, 0x40000040)
              ).ljust(FILE_ALIGN, b'\x00')

    text = (DLLMAIN + bytes([0xC3] * n)).ljust(text_rawsz, b'\x00')
    return header + text + edata


# ── Stub DLL definitions ─────────────────────────────────────────────────────

_STUBS = {
    'designide60.bpl': [
        b'@Designintf@initialization$qqrv',
        b'@Designintf@Finalization$qqrv',
        b'@Designintf@RegisterPropertyEditor$qqrp17Typinfo@TTypeInfop17System@TMetaClassx17System@AnsiStringt2',
        b'@Designintf@RegisterComponentEditor$qqrp17System@TMetaClasst1',
        b'@$xp$20Designintf@IDesigner',
        b'@Designmenus@initialization$qqrv',
        b'@Designmenus@Finalization$qqrv',
        b'@Designeditors@initialization$qqrv',
        b'@Designeditors@Finalization$qqrv',
        b'@Designeditors@TComponentEditor@PrepareItem$qqrix49System@%DelphiInterface$t21Designmenus@IMenuItem%',
        b'@Designeditors@TComponentEditor@Copy$qqrv',
        b'@Designeditors@TComponentEditor@GetDesigner$qqrv',
        b'@Designeditors@TComponentEditor@Edit$qqrv',
        b'@Designeditors@TComponentEditor@$bctr$qqrp18Classes@TComponent48System@%DelphiInterface$t20Designintf@IDesigner%',
        b'@Designeditors@TClassProperty@GetValue$qqrv',
        b'@Designeditors@TClassProperty@GetProperties$qqrynpqqrx48System@%DelphiInterface$t20Designintf@IProperty%$v',
        b'@Designeditors@TClassProperty@GetAttributes$qqrv',
        b'@Designeditors@TPropertyEditor@SetValue$qqrx17System@AnsiString',
        b'@Designeditors@TPropertyEditor@SetStrValue$qqrx17System@AnsiString',
        b'@Designeditors@TPropertyEditor@SetPropEntry$qqrip19Classes@TPersistentp17Typinfo@TPropInfo',
        b'@Designeditors@TPropertyEditor@Initialize$qqrv',
        b'@Designeditors@TPropertyEditor@GetValues$qqrynpqqrx17System@AnsiString$v',
        b'@Designeditors@TPropertyEditor@GetStrValue$qqrv',
        b'@Designeditors@TPropertyEditor@GetPropInfo$qqrv',
        b'@Designeditors@TPropertyEditor@GetOrdValue$qqrv',
        b'@Designeditors@TPropertyEditor@GetName$qqrv',
        b'@Designeditors@TPropertyEditor@GetEditLimit$qqrv',
        b'@Designeditors@TPropertyEditor@GetComponent$qqri',
        b'@Designeditors@TPropertyEditor@AutoFill$qqrv',
        b'@Designeditors@TPropertyEditor@AllEqual$qqrv',
        b'@Designeditors@TPropertyEditor@Activate$qqrv',
        b'@Designeditors@TPropertyEditor@$bdtr$qqrv',
        b'@Designeditors@TPropertyEditor@$bctr$qqrx48System@%DelphiInterface$t20Designintf@IDesigner%i',
        b'@$xp$30Designeditors@TComponentEditor',
        b'@Designeditors@TComponentEditor@',
        b'@$xp$28Designeditors@TClassProperty',
        b'@Designeditors@TClassProperty@',
        b'@Proxies@initialization$qqrv',
        b'@Proxies@Finalization$qqrv',
        b'@Designconst@initialization$qqrv',
        b'@Designconst@Finalization$qqrv',
        b'@Vcleditors@initialization$qqrv',
        b'@Vcleditors@Finalization$qqrv',
        b'@Stredit@initialization$qqrv',
        b'@Stredit@Finalization$qqrv',
        b'@Stredit@TStringListProperty@Edit$qqrv',
        b'@Stredit@TStringListProperty@EditDialog$qqrv',
        b'@$xp$27Stredit@TStringListProperty',
        b'@Stredit@TStringListProperty@',
        b'@Valueedit@initialization$qqrv',
        b'@Valueedit@Finalization$qqrv',
        b'@Stringsedit@initialization$qqrv',
        b'@Stringsedit@Finalization$qqrv',
        b'@Stfilsys@initialization$qqrv',
        b'@Stfilsys@Finalization$qqrv',
        b'@Toolsapi@initialization$qqrv',
        b'@Toolsapi@Finalization$qqrv',
        b'@Istreams@initialization$qqrv',
        b'@Istreams@Finalization$qqrv',
        b'@Virtintf@initialization$qqrv',
        b'@Virtintf@Finalization$qqrv',
        b'@Componentdesigner@initialization$qqrv',
        b'@Componentdesigner@Finalization$qqrv',
        b'@Editintf@initialization$qqrv',
        b'@Editintf@Finalization$qqrv',
        b'@Events@initialization$qqrv',
        b'@Events@Finalization$qqrv',
        b'@Toolutils@initialization$qqrv',
        b'@Toolutils@Finalization$qqrv',
        b'@Update@initialization$qqrv',
        b'@Update@Finalization$qqrv',
        b'@Libconst@initialization$qqrv',
        b'@Libconst@Finalization$qqrv',
        b'@Designer@initialization$qqrv',
        b'@Designer@Finalization$qqrv',
        b'@Toplevels@initialization$qqrv',
        b'@Toplevels@Finalization$qqrv',
        b'@Treeintf@initialization$qqrv',
        b'@Treeintf@Finalization$qqrv',
        b'@Designwindows@initialization$qqrv',
        b'@Designwindows@Finalization$qqrv',
    ],
    'dcldxgredd6.bpl': [
        b'@Dxtlreg@initialization$qqrv',
        b'@Dxtlreg@Finalization$qqrv',
        b'@Dxtlreg@AddTreeListColumns$qqrpxp17System@TMetaClassxi',
        b'@Dxtledit@initialization$qqrv',
        b'@Dxtledit@Finalization$qqrv',
        b'@Dxtledtr@initialization$qqrv',
        b'@Dxtledtr@Finalization$qqrv',
        b'@Dxgrcola@initialization$qqrv',
        b'@Dxgrcola@Finalization$qqrv',
        b'@Dxdbtl@initialization$qqrv',
        b'@Dxdbtl@Finalization$qqrv',
    ],
    'dclrxall6.bpl': [
        b'@Presrdsn@initialization$qqrv',
        b'@Presrdsn@Finalization$qqrv',
        b'@Presrdsn@TFormStorageEditor@GetVerb$qqri',
        b'@Presrdsn@TFormStorageEditor@ExecuteVerb$qqri',
        b'@$xp$27Presrdsn@TFormStorageEditor',
        b'@Presrdsn@TFormStorageEditor@',
        b'@Rxlconst@initialization$qqrv',
        b'@Rxlconst@Finalization$qqrv',
    ],
}


def ensure_stubs():
    """Create stub DLLs in BPL_DIR if they don't already exist."""
    for dll_name, exports in _STUBS.items():
        path = os.path.join(BPL_DIR, dll_name)
        if not os.path.exists(path):
            data = _build_stub_pe(dll_name, exports)
            with open(path, 'wb') as f:
                f.write(data)


# ── PowerShell keygen script ─────────────────────────────────────────────────
# Calls three Delphi register-convention functions:
#   1. TRegData constructor (0x36AFC) — EAX=class_ref, DL=1 → returns obj
#   2. decode_inst_key (0x405F0) — EAX=machineID, EDX=obj, ECX=internalKey
#   3. make_inst_key (0x40300) — EAX=obj, EDX=internalKey, ECX=output_ref → flat 28-char key

_PS_SCRIPT = r"""
param([string]$MachineID, [string]$Dir, [string]$InternalKey, [string]$AppName)

Add-Type -CompilerParameters (New-Object System.CodeDom.Compiler.CompilerParameters -Property @{
    CompilerOptions = '/unsafe /platform:x86'
    GenerateInMemory = $true
}) -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Text;

public unsafe class KeyGen {
    [DllImport("kernel32.dll")] static extern bool SetDllDirectory(string d);
    [DllImport("kernel32.dll")] static extern bool SetCurrentDirectory(string d);
    [DllImport("kernel32.dll", SetLastError=true)] static extern IntPtr LoadLibrary(string p);
    [DllImport("kernel32.dll")] static extern IntPtr VirtualAlloc(IntPtr a, uint s, uint t, uint p);
    [DllImport("kernel32.dll")] static extern bool VirtualFree(IntPtr a, uint s, uint t);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void VoidFn3(IntPtr a1, IntPtr a2, IntPtr a3);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate IntPtr RetFn3(IntPtr a1, IntPtr a2, IntPtr a3);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void VoidFn4(IntPtr a1, IntPtr a2, IntPtr a3, IntPtr a4);

    static IntPtr MkThunk3(IntPtr fn) {
        // Thunk: [esp+4]->EAX, [esp+8]->EDX, [esp+12]->ECX, JMP fn
        IntPtr t = VirtualAlloc(IntPtr.Zero, 32, 0x3000, 0x40);
        byte* p = (byte*)t;
        p[0]=0x8B; p[1]=0x44; p[2]=0x24; p[3]=0x04;
        p[4]=0x8B; p[5]=0x54; p[6]=0x24; p[7]=0x08;
        p[8]=0x8B; p[9]=0x4C; p[10]=0x24; p[11]=0x0C;
        p[12]=0xE9;
        long d = fn.ToInt64() - (t.ToInt64() + 17);
        p[13]=(byte)d; p[14]=(byte)(d>>8); p[15]=(byte)(d>>16); p[16]=(byte)(d>>24);
        return t;
    }

    static IntPtr MkCtorThunk(IntPtr fn) {
        // Thunk: [esp+4]->EAX, DL=1, JMP fn  (TRegData constructor)
        IntPtr t = VirtualAlloc(IntPtr.Zero, 32, 0x3000, 0x40);
        byte* p = (byte*)t;
        p[0]=0x8B; p[1]=0x44; p[2]=0x24; p[3]=0x04;  // MOV EAX,[ESP+4]
        p[4]=0xB2; p[5]=0x01;                           // MOV DL,1
        p[6]=0xE9;
        long d = fn.ToInt64() - (t.ToInt64() + 11);
        p[7]=(byte)d; p[8]=(byte)(d>>8); p[9]=(byte)(d>>16); p[10]=(byte)(d>>24);
        return t;
    }

    static IntPtr MkThunk4(IntPtr fn) {
        // Thunk: [esp+4]->EAX, [esp+8]->EDX, [esp+12]->ECX, [esp+16]->[esp+4], JMP fn
        // For Delphi: EAX=p1, EDX=p2, ECX=p3, stack[esp+4]=p4; callee does ret 4
        IntPtr t = VirtualAlloc(IntPtr.Zero, 64, 0x3000, 0x40);
        byte* p = (byte*)t;
        p[0]=0x8B; p[1]=0x44; p[2]=0x24; p[3]=0x04;    // MOV EAX,[ESP+4]
        p[4]=0x8B; p[5]=0x54; p[6]=0x24; p[7]=0x08;    // MOV EDX,[ESP+8]
        p[8]=0x8B; p[9]=0x4C; p[10]=0x24; p[11]=0x0C;  // MOV ECX,[ESP+12]
        p[12]=0x8B; p[13]=0x5C; p[14]=0x24; p[15]=0x10; // MOV EBX,[ESP+16]
        p[16]=0x89; p[17]=0x5C; p[18]=0x24; p[19]=0x04; // MOV [ESP+4],EBX
        p[20]=0xE9;
        long d2 = fn.ToInt64() - (t.ToInt64() + 25);
        p[21]=(byte)d2; p[22]=(byte)(d2>>8); p[23]=(byte)(d2>>16); p[24]=(byte)(d2>>24);
        return t;
    }

    static byte[] MkStr(string s) {
        var c = Encoding.GetEncoding(1252).GetBytes(s);
        var b = new byte[8 + c.Length + 1];
        b[0]=b[1]=b[2]=b[3]=0xFF;
        b[4]=(byte)c.Length; b[5]=(byte)(c.Length>>8);
        b[6]=(byte)(c.Length>>16); b[7]=(byte)(c.Length>>24);
        Array.Copy(c, 0, b, 8, c.Length);
        return b;
    }

    static string ReadStr(IntPtr ptrToStrPtr) {
        int sp = Marshal.ReadInt32(ptrToStrPtr);
        if (sp == 0) return "ERROR:nil";
        int len = Marshal.ReadInt32(new IntPtr(sp - 4));
        if (len <= 0 || len > 1000) return "ERROR:len=" + len;
        byte[] kb = new byte[len];
        for (int i = 0; i < len; i++) kb[i] = Marshal.ReadByte(new IntPtr(sp + i));
        return Encoding.GetEncoding(1252).GetString(kb);
    }

    // Allocate a Delphi-format AnsiString on the native heap (not GC'd).
    // Returns pointer to the chars (= Delphi AnsiString value).
    // Header: refcount=-1 (static), length=n. Must be freed with FreeHGlobal(ptr-8).
    static IntPtr MkStrNative(string s) {
        var c = Encoding.GetEncoding(1252).GetBytes(s);
        int n = c.Length;
        IntPtr hdr = Marshal.AllocHGlobal(8 + n + 1);
        Marshal.WriteInt32(hdr, 0, -1);     // refcount = -1 (static, never freed by Delphi)
        Marshal.WriteInt32(hdr, 4, n);      // length
        for (int i = 0; i < n; i++) Marshal.WriteByte(hdr, 8 + i, c[i]);
        Marshal.WriteByte(hdr, 8 + n, 0);  // null terminator
        return new IntPtr(hdr.ToInt64() + 8); // return ptr to chars
    }

    public static string Generate(string bplDir, string machineId, string internalKey, string appName) {
        SetDllDirectory(bplDir);
        SetCurrentDirectory(bplDir);
        IntPtr hbpl = LoadLibrary(Path.Combine(bplDir, "cbs_msdx_reg.bpl"));
        if (hbpl == IntPtr.Zero)
            return "ERROR:LoadLibrary=" + Marshal.GetLastWin32Error();
        long B = hbpl.ToInt64();

        var machBuf = MkStr(machineId);
        string result = "ERROR:init";

        fixed (byte* mp = machBuf) {
            IntPtr machChars = new IntPtr(mp + 8);

            // ── Step 0: Compute transformed internalKey E via fn_0x4427ec ──────────
            // fn_0x4427ec(EAX=outer_obj, EDX=&output_E):
            //   calls fn_0x44212c(outer_obj, &local1)  — skipped when outer_obj[0x42]=0
            //   calls fn_0x442090(outer_obj, &local2)  — needs outer_obj[0x4A]!=0
            //   calls fn_0x4400cc(local2, local1, outer_obj[0x30], &output_E)
            //     → cipher1(class 0x43C4A4) keyed with outer_obj[0x30], encrypts it
            //     → FmtStr("%s%s%s",[local2,local1,enc_key]) → E_intermediate
            //     → cipher2(class 0x43B070) keyed with E_intermediate, encrypts it → E

            // Native-heap copies: internalKey is encrypted in-place by fn_0x4400cc;
            // appName is the OnGetAppName result (local2 in E computation).
            IntPtr ikNative = MkStrNative(internalKey);
            IntPtr anNative = MkStrNative(appName);
            IntPtr l1Native = MkStrNative("3");  // GuzoPazGetVer returns '3'

            // Trampoline for [0x48] (local2 = OnGetAppName = appName):
            //   fn_0x442090 calls: tramp(EAX=[0x4C], EDX=&local2)
            //   XCHG EAX,EDX → EAX=&local2, EDX=anNative; JMP LStrAsg
            IntPtr tramT = VirtualAlloc(IntPtr.Zero, 32, 0x3000, 0x40);
            byte* pt = (byte*)tramT;
            pt[0] = 0x92; pt[1] = 0xE9;
            long dtram = (B + 0x1160) - (tramT.ToInt64() + 6);
            pt[2]=(byte)dtram; pt[3]=(byte)(dtram>>8);
            pt[4]=(byte)(dtram>>16); pt[5]=(byte)(dtram>>24);

            // Trampoline for [0x40] (local1 = OnGetVer = '3'):
            //   fn_0x44212c calls: tramp(EAX=[0x44], EDX=&local1)
            //   XCHG EAX,EDX → EAX=&local1, EDX=l1Native; JMP LStrAsg
            //   Word at [0x42] == high word of tramL1 addr (VirtualAlloc >= 0x10000, so != 0)
            //   — that non-zero word is what fn_0x44212c checks to decide local1 is assigned.
            IntPtr tramL1 = VirtualAlloc(IntPtr.Zero, 32, 0x3000, 0x40);
            byte* pl1 = (byte*)tramL1;
            pl1[0] = 0x92; pl1[1] = 0xE9;
            long dtramL1 = (B + 0x1160) - (tramL1.ToInt64() + 6);
            pl1[2]=(byte)dtramL1; pl1[3]=(byte)(dtramL1>>8);
            pl1[4]=(byte)(dtramL1>>16); pl1[5]=(byte)(dtramL1>>24);

            IntPtr fakeObj = Marshal.AllocHGlobal(256);
            for (int i = 0; i < 256; i++) Marshal.WriteByte(fakeObj, i, 0);
            Marshal.WriteInt32(fakeObj, 0x30, (int)ikNative.ToInt32());
            // [0x40]=local1 code_ptr; [0x42]=high word of [0x40] (non-zero → local1 event fires)
            Marshal.WriteInt32(fakeObj, 0x40, (int)tramL1.ToInt32());
            Marshal.WriteInt32(fakeObj, 0x44, (int)l1Native.ToInt32());
            Marshal.WriteInt32(fakeObj, 0x48, (int)tramT.ToInt32());
            Marshal.WriteInt32(fakeObj, 0x4C, (int)anNative.ToInt32());

            IntPtr pEStr = Marshal.AllocHGlobal(4);
            Marshal.WriteInt32(pEStr, 0);

            IntPtr trT = MkThunk3(new IntPtr(B + 0x427EC));
            Marshal.GetDelegateForFunctionPointer<VoidFn3>(trT)(fakeObj, pEStr, IntPtr.Zero);
            VirtualFree(trT, 0, 0x8000);

            IntPtr eStr = new IntPtr(Marshal.ReadInt32(pEStr));
            Marshal.FreeHGlobal(pEStr);
            Marshal.FreeHGlobal(fakeObj);
            VirtualFree(tramT, 0, 0x8000);
            VirtualFree(tramL1, 0, 0x8000);
            Marshal.FreeHGlobal(new IntPtr(ikNative.ToInt64() - 8));
            Marshal.FreeHGlobal(new IntPtr(anNative.ToInt64() - 8));
            Marshal.FreeHGlobal(new IntPtr(l1Native.ToInt64() - 8));

            if (eStr == IntPtr.Zero) return "ERROR:E=nil";
            // Debug: report E length
            int eLen = Marshal.ReadInt32(new IntPtr(eStr.ToInt64() - 4));
            // (eLen should be 40 for the doubled-then-re-encrypted key)

            // ── Step 1: Create TRegData object ──────────────────────────────────────
            int classRef = Marshal.ReadInt32(new IntPtr(B + 0x369E4));
            IntPtr ctorT = MkCtorThunk(new IntPtr(B + 0x36AFC));
            IntPtr obj = Marshal.GetDelegateForFunctionPointer<RetFn3>(ctorT)(
                new IntPtr(classRef), IntPtr.Zero, IntPtr.Zero);
            VirtualFree(ctorT, 0, 0x8000);
            if (obj == IntPtr.Zero) return "ERROR:ctor=nil";

            // ── Step 2: decode_inst_key(EAX=machineId, EDX=obj, ECX=E) ─────────────
            // Using E (transformed internalKey) instead of raw internalKey string
            IntPtr decT = MkThunk3(new IntPtr(B + 0x405F0));
            Marshal.GetDelegateForFunctionPointer<VoidFn3>(decT)(machChars, obj, eStr);
            VirtualFree(decT, 0, 0x8000);

            // 3. Zero expiry/flag fields → permanent key
            Marshal.WriteByte(obj, 0x19, 0);
            Marshal.WriteByte(obj, 0x1A, 0);
            Marshal.WriteByte(obj, 0x1B, 0);

            // 4. GetBytes(EAX=obj, EDX=pBufPtr, ECX=pBufLen) → 14-byte plaintext
            IntPtr pBufPtr = Marshal.AllocHGlobal(4); Marshal.WriteInt32(pBufPtr, 0);
            IntPtr pBufLen = Marshal.AllocHGlobal(4); Marshal.WriteInt32(pBufLen, 0);
            IntPtr gbT = MkThunk3(new IntPtr(B + 0x36B68));
            Marshal.GetDelegateForFunctionPointer<VoidFn3>(gbT)(obj, pBufPtr, pBufLen);
            VirtualFree(gbT, 0, 0x8000);
            IntPtr buf = new IntPtr(Marshal.ReadInt32(pBufPtr));
            Marshal.FreeHGlobal(pBufPtr); Marshal.FreeHGlobal(pBufLen);
            if (buf == IntPtr.Zero) return "ERROR:GetBytes=nil";

            // 5. Create Delphi strings: M = buf[0..7] (8 bytes), KEY2 = buf[7..13] (7 bytes)
            IntPtr pMStr  = Marshal.AllocHGlobal(4); Marshal.WriteInt32(pMStr, 0);
            IntPtr pK2Str = Marshal.AllocHGlobal(4); Marshal.WriteInt32(pK2Str, 0);
            IntPtr mkStrT = MkThunk3(new IntPtr(B + 0x2E4E8));
            Marshal.GetDelegateForFunctionPointer<VoidFn3>(mkStrT)(buf, new IntPtr(8), pMStr);
            Marshal.GetDelegateForFunctionPointer<VoidFn3>(mkStrT)(
                new IntPtr(buf.ToInt64() + 7), new IntPtr(7), pK2Str);
            VirtualFree(mkStrT, 0, 0x8000);
            IntPtr mStr  = new IntPtr(Marshal.ReadInt32(pMStr));
            IntPtr k2Str = new IntPtr(Marshal.ReadInt32(pK2Str));
            Marshal.FreeHGlobal(pMStr); Marshal.FreeHGlobal(pK2Str);

            IntPtr sdT = MkThunk3(new IntPtr(B + 0x2D4BC));

            // 6. Inner cipher (class 0x438F64, ctor 0x439030): encrypt buf[0..6] with KEY2
            int iCls = Marshal.ReadInt32(new IntPtr(B + 0x38F64));
            IntPtr iCtorT = MkCtorThunk(new IntPtr(B + 0x39030));
            IntPtr iCiph = Marshal.GetDelegateForFunctionPointer<RetFn3>(iCtorT)(
                new IntPtr(iCls), IntPtr.Zero, IntPtr.Zero);
            VirtualFree(iCtorT, 0, 0x8000);
            Marshal.GetDelegateForFunctionPointer<VoidFn3>(sdT)(iCiph, k2Str, IntPtr.Zero);
            IntPtr iVMT   = new IntPtr(Marshal.ReadInt32(iCiph));
            IntPtr fnIEnc = new IntPtr(Marshal.ReadInt32(new IntPtr(iVMT.ToInt64() + 0x4C)));
            IntPtr iEncT  = MkThunk4(fnIEnc);
            Marshal.GetDelegateForFunctionPointer<VoidFn4>(iEncT)(iCiph, buf, buf, new IntPtr(7));
            VirtualFree(iEncT, 0, 0x8000);

            // 7. Outer cipher (class 0x43A25C, ctor 0x43A328): encrypt buf[0..13] with M
            int oCls = Marshal.ReadInt32(new IntPtr(B + 0x3A25C));
            IntPtr oCtorT = MkCtorThunk(new IntPtr(B + 0x3A328));
            IntPtr oCiph = Marshal.GetDelegateForFunctionPointer<RetFn3>(oCtorT)(
                new IntPtr(oCls), IntPtr.Zero, IntPtr.Zero);
            VirtualFree(oCtorT, 0, 0x8000);
            Marshal.GetDelegateForFunctionPointer<VoidFn3>(sdT)(oCiph, mStr, IntPtr.Zero);
            VirtualFree(sdT, 0, 0x8000);
            IntPtr oVMT   = new IntPtr(Marshal.ReadInt32(oCiph));
            IntPtr fnOEnc = new IntPtr(Marshal.ReadInt32(new IntPtr(oVMT.ToInt64() + 0x4C)));
            IntPtr oEncT  = MkThunk4(fnOEnc);
            Marshal.GetDelegateForFunctionPointer<VoidFn4>(oEncT)(oCiph, buf, buf, new IntPtr(14));
            VirtualFree(oEncT, 0, 0x8000);

            // 8. Base-26 encode: 14 encrypted bytes → 28 A-Z chars
            var sb = new StringBuilder(28);
            for (int i = 0; i < 14; i++) {
                int bv = Marshal.ReadByte(buf, i);
                sb.Append((char)('A' + bv / 26));
                sb.Append((char)('A' + bv % 26));
            }
            result = sb.ToString();
        }
        return result;
    }
}
'@

$key = [KeyGen]::Generate($Dir, $MachineID, $InternalKey, $AppName)
Write-Output $key
"""


def _add_dashes(flat):
    """Format a 28-char flat key as 3-5-5-5-5-5 (same format as machine ID)."""
    if len(flat) != 28:
        return flat  # return as-is if unexpected length
    return '-'.join([flat[0:3], flat[3:8], flat[8:13], flat[13:18], flat[18:23], flat[23:28]])


def generate_key(machine_id, internal_key=INTERNAL_KEY, app_name=APP_NAME, bpl_dir=BPL_DIR):
    """Generate a registration key for the given machine ID."""
    ensure_stubs()

    # Write PS script to a temp file
    with tempfile.NamedTemporaryFile(suffix='.ps1', mode='w',
                                     delete=False, encoding='utf-8') as f:
        f.write(_PS_SCRIPT)
        ps_path = f.name

    try:
        result = subprocess.run(
            [PS32, '-ExecutionPolicy', 'Bypass', '-File', ps_path,
             '-MachineID', machine_id,
             '-Dir', bpl_dir,
             '-InternalKey', internal_key,
             '-AppName', app_name],
            capture_output=True, text=True, timeout=30
        )
        flat = result.stdout.strip()
        if result.returncode != 0 or flat.startswith('ERROR'):
            err = result.stderr.strip() or flat
            raise RuntimeError(f'Keygen failed: {err}')
        return _add_dashes(flat)
    finally:
        os.unlink(ps_path)


if __name__ == '__main__':
    machine_id = sys.argv[1] if len(sys.argv) > 1 else 'ERI-LAJJS-FAAZI-IEOBO-AQIGD-XGSIZ'
    print(f'Machine ID : {machine_id}')
    try:
        key = generate_key(machine_id)
        print(f'REG KEY    : {key}')
    except Exception as e:
        print(f'ERROR: {e}', file=sys.stderr)
        sys.exit(1)
