内容纲要
C++ ffi
LPCSTR ref.refType(ref.types.CString)
void* ref.refType(ref.types.void)
const ffi = require('ffi-napi');
const ref = require('ref-napi');
const refStruct = require('ref-struct-napi');
const os = require('os');
const Buffer = require('buffer').Buffer;

let sp, up;
if (os.arch() == 'x64') {
    sp = ref.types.longlong;
    up = ref.types.ulonglong;
} else {
    sp = ref.types.int;
    up = ref.types.uint;
}

let VOID = ref.types.void;
let CHAR = ref.types.char;
let UCHAR = ref.types.uchar;
let SHORT = ref.types.short;
let USHORT = ref.types.ushort;
let INT = ref.types.int;
let UINT = ref.types.uint;
let LONG = ref.types.long;
let ULONG = ref.types.ulong;
let LONGLONG = ref.types.longlong;
let ULONGLONG = ref.types.ulonglong;

let BYTE = UCHAR;
let WORD = USHORT;
let BOOL = INT;
let DWORD = ULONG;
let WCHAR = WORD;

let INT_PTR = sp;
let UINT_PTR = up;
let LONG_PTR = sp;
let ULONG_PTR = up;

let WPARAM = UINT_PTR;
let LPARAM = LONG_PTR;
let LRESULT = LONG_PTR;
let HRESULT = LONG;

let COORD = refStruct({
    X: SHORT,
    Y: SHORT
});

let SMALL_RECT = refStruct({
    L: SHORT,
    T: SHORT,
    R: SHORT,
    B: SHORT
});

let CHAR_INFO = refStruct({
    C: WCHAR,
    A: WORD
});

let INPUT_RECORD = refStruct({
    EventType: WORD,
    PAD: WORD,
    PAD1: WORD,
    PAD2: WORD,
    PAD3: WORD,
    PAD4: WORD,
    PAD5: WORD,
    PAD6: WORD,
    PAD7: WORD,
    PAD8: WORD,
});

let CONSOLE_SCREEN_BUFFER_INFO = refStruct({
    dwSize: COORD,
    dwCursorPosition: COORD,
    wAttributes: WORD,
    srWindow: SMALL_RECT,
    dwMaximumWindowSize: COORD
});

let CONSOLE_CURSOR_INFO = refStruct({
    dwSize: DWORD,
    bVisible: BOOL
});

let PVOID = ref.refType(ref.types.void);
let PUCHAR = ref.refType(UCHAR);
let PWCHAR = ref.refType(WCHAR);
let PUSHORT = ref.refType(USHORT);
let PWORD = ref.refType(WORD);
let PULONG = ref.refType(ULONG);
let PDWORD = ref.refType(DWORD);
let HANDLE = PVOID;
let PHANDLE = ref.refType(HANDLE);

let PSMALL_RECT = ref.refType(SMALL_RECT);
let PCHAR_INFO = ref.refType(CHAR_INFO);
let PINPUT_RECORD = ref.refType(INPUT_RECORD);
let PCONSOLE_SCREEN_BUFFER_INFO = ref.refType(CONSOLE_SCREEN_BUFFER_INFO);
let PCONSOLE_CURSOR_INFO = ref.refType(CONSOLE_CURSOR_INFO);

function prepareCache(type, length) {
    let buf = Buffer.alloc(type.size * length);
    buf.type = type;
    return buf;
}

function enumCache(buf, type, length, callback) {
    for (let i = 0; i < length; ++i) {
        let temp = ref.reinterpret(buf, type.size, type.size * i);
        temp.type = type;
        temp = callback(temp, i);
        if (temp) {
            ref.set(buf, type.size * i, temp, type);
        }
    }
}

function parseInputRecord(e) {
    switch (e.EventType) {
    case 1:
        return {
            EventType: 1,
            bKeyDown: e.PAD1 != 0,
            wRepeatCount: e.PAD3,
            wVirtualKeyCode: e.PAD4,
            wVirtualScanCode: e.PAD5,
            UnicodeChar: e.PAD6,
            dwControlKeyState: e.PAD8 << 16 | e.PAD7
        };
    case 2:
        return {
            EventType: 2,
            dwMousePosition: {
                X: e.PAD1,
                Y: e.PAD2
            },
            dwButtonState: e.PAD4 << 16 | e.PAD3,
            dwControlKeyState: e.PAD6 << 16 | e.PAD5,
            dwEventFlags: e.PAD8 << 16 | e.PAD7
        };
    case 4:
        return {
            EventType: 4,
            dwSize: {
                X: e.PAD1,
                Y: e.PAD2
            }
        };
    case 8:
        return {
            EventType: 8,
            dwCommandId: e.PAD2 << 16 | e.PAD1
        };
    case 16:
        return {
            EventType: 16,
            bSetFocus: e.PAD1 != 0
        };
    }
}

let kernel32 = ffi.Library('kernel32.dll', {
    'CloseHandle': [ BOOL, [ HANDLE ]],
    'CreateConsoleScreenBuffer': [ HANDLE, [ DWORD, DWORD, PVOID, DWORD, PVOID ]],
    'FillConsoleOutputAttribute': [ BOOL, [ HANDLE, WORD, DWORD, COORD, PDWORD ]],
    'FillConsoleOutputCharacterW': [ BOOL, [ HANDLE, WORD, DWORD, COORD, PDWORD ]],
    'FlushConsoleInputBuffer': [ BOOL, [ HANDLE ]],
    'GetConsoleScreenBufferInfo': [ BOOL, [ HANDLE, PCONSOLE_SCREEN_BUFFER_INFO ]],
    'GetNumberOfConsoleInputEvents': [ BOOL, [ HANDLE, PDWORD ]],
    'GetStdHandle': [ HANDLE, [ DWORD ]],
    'PeekConsoleInputW': [ BOOL, [ HANDLE, PINPUT_RECORD, DWORD, PDWORD ]],
    'ReadConsoleInputW': [ BOOL, [ HANDLE, PINPUT_RECORD, DWORD, PDWORD ]],
    'ReadConsoleOutputW': [ BOOL, [ HANDLE, PCHAR_INFO, COORD, COORD, PSMALL_RECT ]],
    'ReadConsoleOutputAttribute': [ BOOL, [ HANDLE, PWORD, DWORD, COORD, PDWORD ]],
    'ReadConsoleOutputCharacterW': [ BOOL, [ HANDLE, PWCHAR, DWORD, COORD, PDWORD ]],
    'ScrollConsoleScreenBufferW': [ BOOL, [ HANDLE, PSMALL_RECT, PSMALL_RECT, COORD, PCHAR_INFO ]],
    'SetConsoleActiveScreenBuffer': [ BOOL, [ HANDLE ]],
    'SetConsoleCP': [ BOOL, [ UINT ]],
    'SetConsoleCursorInfo': [ BOOL, [ HANDLE, PCONSOLE_CURSOR_INFO ]],
    'SetConsoleCursorPosition': [ BOOL, [ HANDLE, COORD ]],
    'SetConsoleMode': [ BOOL, [ HANDLE, DWORD ]],
    'SetConsoleOutputCP': [ BOOL, [ UINT ]],
    'SetConsoleScreenBufferSize': [ BOOL, [ HANDLE, COORD ]],
    'SetConsoleTextAttribute': [ BOOL, [ HANDLE, WORD ]],
    'SetConsoleTitleW': [ BOOL, [ PWCHAR ]],
    'SetConsoleWindowInfo': [ HANDLE, [ HANDLE, BOOL, PSMALL_RECT ]],
    'WriteConsoleW': [ BOOL, [ HANDLE, PWCHAR, DWORD, PDWORD, PVOID ]],
    'WriteConsoleOutputW': [ BOOL, [ HANDLE, PCHAR_INFO, COORD, COORD, PSMALL_RECT ]],
    'WriteConsoleOutputAttribute': [ BOOL, [ HANDLE, PWORD, DWORD, COORD, PDWORD ]],
    'WriteConsoleOutputCharacterW': [ BOOL, [ HANDLE, PWCHAR, DWORD, COORD, PDWORD ]]
});

const wincon = {
    ATTR: (df, db) => { // fore_red + back_white + back_intense make 'R' '+RGB'
        let a = 0;
        df.split('').forEach(s => {
            switch (s) {
            case '+':
                a = a + wincon.FOREGROUND_INTENSIVE;
                break;
            case 'R':
                a = a + wincon.FOREGROUND_RED;
                break;
            case 'G':
                a = a + wincon.FOREGROUND_GREEN;
                break;
            case 'B':
                a = a + wincon.FOREGROUND_BLUE;
                break;
            }
        });
        db.split('').forEach(s => {
            switch (s) {
            case '+':
                a = a + wincon.BACKGROUND_INTENSIVE;
                break;
            case 'R':
                a = a + wincon.BACKGROUND_RED;
                break;
            case 'G':
                a = a + wincon.BACKGROUND_GREEN;
                break;
            case 'B':
                a = a + wincon.BACKGROUND_BLUE;
                break;
            }
        });
        return a;
    },
    ...require('./wincondef'),
    CloseHandle: (handle) => {
        return {
            ret: kernel32.CloseHandle(handle) != 0
        };
    },
    CreateConsoleScreenBuffer: (access, share) => {
        return {
            ret: kernel32.CreateConsoleScreenBuffer(access, share, ref.NULL, wincon.CONSOLE_TEXTMODE_BUFFER, ref.NULL)
        };
    },
    FillConsoleOutputAttribute: (handle, attrib, length, position) => {
        let pos = new COORD(position);
        let n = ref.alloc(DWORD);
        let rs = kernel32.FillConsoleOutputAttribute(handle, attrib, length, pos, n);
        return {
            ret: rs != 0,
            ex: n.deref()
        };
    },
    FillConsoleOutputCharacter: (handle, char, length, position) => {
        let pos = new COORD(position);
        let n = ref.alloc(DWORD);
        let rs = kernel32.FillConsoleOutputCharacterW(handle, char, length, pos, n);
        return {
            ret: rs != 0,
            ex: n.deref()
        };
    },
    FlushConsoleInputBuffer: (handle) => {
        return {
            ret: kernel32.FlushConsoleInputBuffer(handle) != 0
        };
    },
    GetConsoleScreenBufferInfo: (handle) => {
        let i = ref.alloc(CONSOLE_SCREEN_BUFFER_INFO);
        let rs = kernel32.GetConsoleScreenBufferInfo(handle, i);
        let o = i.deref().toObject();
        return {
            ret: rs != 0,
            ex: {
                dwSize: o.dwSize.toObject(),
                dwCursorPosition: o.dwCursorPosition.toObject(),
                wAttributes: o.wAttributes,
                srWindow: o.srWindow.toObject(),
                dwMaximumWindowSize: o.dwMaximumWindowSize.toObject()
            }
        };
    },
    GetNumberOfConsoleInputEvents: (handle) => {
        let n = ref.alloc(DWORD);
        let rs = kernel32.GetNumberOfConsoleInputEvents(handle, n);
        return {
            ret: rs != 0,
            ex: n.deref()
        };
    },
    GetStdHandle: (n) => {
        return {
            ret: kernel32.GetStdHandle(n)
        };
    },
    PeekConsoleInput: (handle, length) => {
        let buf = prepareCache(INPUT_RECORD, length);
        let n = ref.alloc(DWORD);
        let rs = kernel32.PeadConsoleInputW(handle, buf, length, n);
        let data = [];
        enumCache(buf, INPUT_RECORD, n.deref(), (b) => {
            data.push(parseInputRecord(b.deref()));
        });
        return {
            ret: rs != 0,
            ex: data
        };
    },
    ReadConsoleInput: (handle, length) => {
        let buf = prepareCache(INPUT_RECORD, length);
        let n = ref.alloc(DWORD);
        let rs = kernel32.ReadConsoleInputW(handle, buf, length, n);
        let data = [];
        enumCache(buf, INPUT_RECORD, n.deref(), (b) => {
            data.push(parseInputRecord(b.deref()));
        });
        return {
            ret: rs != 0,
            ex: data
        };
    },
    ReadConsoleOutput: (handle, size, pos, region) => {
        let buf = prepareCache(CHAR_INFO, size.X * size.Y);
        let bufsize = new COORD(size);
        let bufcoord = new COORD(pos);
        let wr = ref.alloc(SMALL_RECT, region);
        let rs = kernel32.ReadConsoleOutputW(handle, buf, bufsize, bufcoord, wr);
        let res = [];
        enumCache(buf, CHAR_INFO, size.X * size.Y, (e) => {
            res.push(e.UnicodeChar);
            res.push(e.Attributes);
        });
        return {
            ret: rs != 0,
            ex: wr.deref().toObject(),
            ex2: res
        };
    },
    ReadConsoleOutputAttribute: (handle, length, pos) => {
        let buf = prepareCache(WORD, length);
        let wr = new COORD(pos);
        let n = ref.alloc(DWORD);
        let rs = kernel32.ReadConsoleOutputAttribute(handle, buf, length, wr, n);
        let res = [];
        enumCache(buf, WORD, n.deref(), (e) => {
            res.push(e.deref());
        });
        return {
            ret: rs != 0,
            ex: n.deref(),
            ex2: res
        };
    },
    ReadConsoleOutputCharacter: (handle, length, pos) => {
        let buf = prepareCache(WCHAR, length);
        let wr = new COORD(pos);
        let n = ref.alloc(DWORD);
        let rs = kernel32.ReadConsoleOutputCharacterW(handle, buf, length, wr, n);
        let res = [];
        enumCache(buf, WCHAR, n.deref(), (e) => {
            res.push(e.deref());
        });
        return {
            ret: rs != 0,
            ex: n.deref(),
            ex2: String.fromCharCode(...res)
        };
    },
    ScrollConsoleScreenBuffer: (handle, target, clip, dest, fill) => {
        let sr = ref.alloc(SMALL_RECT, target);
        let cr = clip == null ? ref.NULL : ref.alloc(SMALL_RECT, clip);
        let ci = ref.alloc(CHAR_INFO, fill);
        return {
            ret: kernel32.ScrollConsoleScreenBufferW(handle, sr, cr, new COORD(dest), ci) != 0
        };
    },
    SetConsoleActiveScreenBuffer: (handle) => {
        return {
            ret: kernel32.SetConsoleActiveScreenBuffer(handle) != 0
        };
    },
    SetConsoleCP: (id) => {
        return {
            ret: kernel32.SetConsoleCP(id) != 0
        };
    },
    SetConsoleCursorInfo: (handle, info) => {
        let buf = ref.alloc(CONSOLE_CURSOR_INFO, { dwSize: info.size, bVisible: info.visible ? 1 : 0 });
        return {
            ret: kernel32.SetConsoleCursorInfo(handle, buf) != 0
        };
    },
    SetConsoleCursorPosition: (handle, pos) => {
        return {
            ret: kernel32.SetConsoleCursorPosition(handle, new COORD(pos)) != 0
        };
    },
    SetConsoleMode: (handle, mode) => {
        return {
            ret: kernel32.SetConsoleMode(handle, mode) != 0
        };
    },
    SetConsoleOutputCP: (id) => {
        return {
            ret: kernel32.SetConsoleOutputCP(id) != 0
        };
    },
    SetConsoleScreenBufferSize: (handle, size) => {
        return {
            ret: kernel32.SetConsoleScreenBufferSize(handle, new COORD(size)) != 0
        };
    },
    SetConsoleTextAttribute: (handle, attr) => {
        return {
            ret: kernel32.SetConsoleTextAttribute(handle, attr) != 0
        };
    },
    SetConsoleTitle: (title) => {
        let buf = prepareCache(WCHAR, title.length);
        enumCache(buf, WCHAR, title.length, (e, i) => {
            return title.charCodeAt(i);
        });
        return {
            ret: kernel32.SetConsoleTitleW(buf) != 0
        };
    },
    SetConsoleWindowInfo: (handle, absolute, size) => {
        let buf = ref.alloc(SMALL_RECT, size);
        let rs = kernel32.SetConsoleWindowInfo(handle, absolute ? 1 : 0, buf);
        return {
            ret: rs != 0
        };
    },
    WriteConsole: (handle, str) => {
        let buf =  prepareCache(WCHAR, str.length);
        enumCache(buf, WCHAR, str.length, (e, i) => {
            return str.charCodeAt(i);
        });
        let n = ref.alloc(DWORD);
        let rs = kernel32.WriteConsoleW(handle, buf, str.length, n, ref.NULL);
        return {
            ret: rs != 0,
            ex: n.deref()
        };
    },
    WriteConsoleOutput: (handle, buffer, width, pos, region) => {
        let size = buffer.length / 2;
        let buf = prepareCache(CHAR_INFO, size);
        enumCache(buf, CHAR_INFO, size, (e, i) => {
            return new CHAR_INFO({ UnicodeChar: buffer[i * 2], Attributes: buffer[i * 2 + 1] });
        });
        let height = Math.floor(size / width);
        let bufsize = new COORD({ X: width, Y: height });
        let bufcoord = new COORD(pos);
        let wr = ref.alloc(SMALL_RECT, region);
        let rs = kernel32.WriteConsoleOutputW(handle, buf, bufsize, bufcoord, wr);
        return {
            ret: rs != 0,
            ex: wr.deref().toObject()
        };
    },
    WriteConsoleOutputAttribute: (handle, buffer, pos) => {
        let buf =  prepareCache(WORD, buffer.length);
        enumCache(buf, WORD, buffer.length, (e, i) => {
            return buffer[i];
        });
        let wr = new COORD(pos);
        let n = ref.alloc(DWORD);
        let rs = kernel32.WriteConsoleOutputAttribute(handle, buf, buffer.length, wr, n);
        return {
            ret: rs != 0,
            ex: n.deref()
        };
    },
    WriteConsoleOutputCharacter: (handle, str, pos) => {
        let buf =  prepareCache(WCHAR, str.length);
        enumCache(buf, WCHAR, str.length, (e, i) => {
            return str.charCodeAt(i);
        });
        let wr = new COORD(pos);
        let n = ref.alloc(DWORD);
        let rs = kernel32.WriteConsoleOutputCharacterW(handle, buf, str.length, wr, n);
        return {
            ret: rs != 0,
            ex: n.deref()
        };
    }
};

module.exports = wincon;
const { execFile } = require('child_process');
const ref = require('ref-napi');
const StructDi = require('ref-struct-di');
const { U, CS, DStruct: DS } = require('win32-api');
const Struct = StructDi(ref);
const user32 = U.load();
const user32e = require('./user32e');
const gdi32 = require('./gdi32');
const config = require('./config.json');
const credentials = require('./credentials.json');

execFile(config.execFilePath);
login();

async function login() {
  const window = await findWindow(config.loginWindowText);
  const controls = getControls(window, config.loginControlIndices);

  input(controls.account, credentials.account);
  await delay(100);
  input(controls.password, credentials.password);
  await delay(1000);
  const captcha = await getCaptcha(controls.captchaImage);
  input(controls.captcha, captcha);

  click(controls.submit);
}

async function findWindow(title) {
  console.log('looking for window with title', title);
  const hwnd = user32.FindWindowExW(0, 0, null, strBuf(title));
  if (isValidHandle(hwnd)) {
    console.log('hwnd', hwnd.toString(16));
    return hwnd;
  } else {
    console.log('try again in 500ms.');
    return new Promise(resolve => {
      setTimeout(() => resolve(findWindow(title)), 500);
    });
  }
}

function isValidHandle(hwnd) {
  return typeof hwnd === 'number' && hwnd > 0
    || typeof hwnd === 'bigint' && hwnd > 0
    || typeof hwnd === 'string' && hwnd.length > 0;
}

function strBuf(str) {
  return Buffer.from(str + '\0', 'ucs2');
}

function getControls(hwnd, indices) {
  const GW_CHILD = 5;
  const GW_HWNDNEXT = 2;

  const controls = {};

  const map = [];
  for (let key in indices) {
    map[indices[key]] = key;
  }

  let childhwnd = user32.GetWindow(hwnd, GW_CHILD);
  let index = 0;
  while (isValidHandle(childhwnd)) {
    if (map[index]) {
      controls[map[index]] = childhwnd;
      console.log(map[index], childhwnd.toString(16));
    }
    childhwnd = user32.GetWindow(childhwnd, GW_HWNDNEXT);
    index++;
  }

  return controls;
}

function input(hwnd, text) {
  user32.SendMessageW(hwnd, CS.WM_SETTEXT, 0, ref.address(strBuf(text)));
}

async function delay(milliseconds) {
  return new Promise(resolve => setTimeout(resolve, milliseconds));
}

function click(hwnd) {
  const BM_CLICK = 0x00F5;
  user32.SendMessageW(hwnd, BM_CLICK, 0, 0);
}

async function getCaptcha(hwnd) {
  screenshot(hwnd, 'captcha.bmp');
  const text = await require("node-tesseract-ocr").recognize('captcha.bmp', { lang: 'eng', oem: 1, psm: 3 })
  console.log('ocr result', text);
  const captcha = text.trim();
  return captcha;
}

function screenshot(hwnd, filePath) {
  const { width, height } = getWindowSize(hwnd);

  const hdcFrom = user32e.GetDC(hwnd);
  const hdcTo = gdi32.CreateCompatibleDC(hdcFrom);
  const hBitmap = gdi32.CreateCompatibleBitmap(hdcFrom, width, height);
  const hLocalBitmap = gdi32.SelectObject(hdcTo, hBitmap);
  const SRCCOPY = 0x00CC0020;
  gdi32.BitBlt(hdcTo, 0, 0, width, height, hdcFrom, 0, 0, SRCCOPY);
  gdi32.SelectObject(hdcTo, hLocalBitmap);
  gdi32.DeleteDC(hdcTo);
  user32e.ReleaseDC(hwnd, hdcFrom);

  const bytes = (width * height) * 4;
  const bmpBuf = Buffer.alloc(bytes);
  gdi32.GetBitmapBits(hBitmap, bytes, bmpBuf);
  gdi32.DeleteObject(hBitmap);

  saveBmp(bmpBuf, width, height, filePath);
}

async function screenshot2(hwnd, filePath) {
  const { width, height } = getWindowSize(hwnd);

  const hdc = user32e.GetDC(hwnd);
  const hdcMem = gdi32.CreateCompatibleDC(hdc);
  const hbitmap = gdi32.CreateCompatibleBitmap(hdc, width, height);
  gdi32.SelectObject(hdcMem, hbitmap);
  user32.PrintWindow(hwnd, hdcMem, 0);

  const bytes = (width * height) * 4;
  const bmpBuf = Buffer.alloc(bytes);
  gdi32.GetBitmapBits(hbitmap, bytes, bmpBuf);
  console.log(bmpBuf);

  gdi32.DeleteObject(hbitmap);
  gdi32.DeleteObject(hdcMem);
  user32e.ReleaseDC(hwnd, hdc);

  saveBmp(bmpBuf, width, height, filePath);
}

function getWindowSize(hwnd) {
  const rect = new Struct(DS.RECT)();
  user32.GetWindowRect(hwnd, rect.ref());
  console.log('rect', rect.left, rect.right, rect.top, rect.bottom);

  const width = rect.right - rect.left - 13;
  const height = rect.bottom - rect.top;

  return { width, height };
}

function saveBmp(buf, width, height, filePath) {
  const imgBuf = require('image-encode')(buf, [width, height], 'bmp');
  require('fs').writeFileSync(filePath, Buffer.from(imgBuf));
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注