npm package: @hamstu/electron-game-overlay
Description: Game overlay system for Electron apps - inject DirectX overlays into games
Latest version: 0.0.8

# @hamstu/electron-game-overlay

A native Node.js addon for rendering Electron windows as overlays inside DirectX games on Windows.

## Features

- Inject overlay DLL into DirectX 9/10/11/12 games
- Render Electron BrowserWindows as game overlays using offscreen rendering
- Forward mouse and keyboard input from games to Electron
- Support for both 32-bit and 64-bit games
- Automatic binary path resolution - just install and use
- **High-level helper API** for easy integration (handles OSR setup, scaling, input translation)

## Installation

```bash
npm install @hamstu/electron-game-overlay
```

## Requirements

- Windows 10/11
- Node.js 14 or later
- Electron 14 or later (for BrowserWindow overlay rendering)

## Quick Start (Recommended)

The easiest way to use this package is with the high-level helper API:

```typescript
import { app, BrowserWindow, ipcMain } from 'electron';
import {
  initializeOverlay,
  OverlayManager,
  WindowPicker,
} from '@hamstu/electron-game-overlay/helpers';

// MUST be called before app.whenReady()
initializeOverlay();

app.whenReady().then(() => {
  // Create the overlay manager
  const overlay = new OverlayManager();
  overlay.start();

  // Create an overlay window (handles OSR setup automatically)
  const window = overlay.createWindow({
    name: 'MyOverlay',
    url: 'file://' + __dirname + '/overlay.html',
    x: 100,
    y: 100,
    width: 640,
    height: 480,
    caption: { height: 40 },  // Makes top 40px draggable
  });

  // Subscribe to events
  overlay.on('connected', (data) => {
    console.log('DLL connected! Process:', data.processId);
  });

  // Find and inject into a game
  const picker = new WindowPicker();
  const game = picker.findByTitle('My Game');
  if (game) {
    const result = overlay.inject(game);
    console.log('Injection result:', result);
  }
});
```

That's it! The helper API handles:
- Off-screen rendering setup
- Display scale factor detection
- Physical/logical pixel coordinate conversion
- Frame buffer transmission
- Input event translation
- Automatic input interception on DLL connect

## Helper API Reference

### `initializeOverlay()`

**Must be called before `app.whenReady()`**. Sets up Electron for overlay usage by:
- Disabling hardware acceleration (required for OSR)
- Adding the no-sandbox flag

### `OverlayManager`

Main orchestration class for managing overlays.

```typescript
const overlay = new OverlayManager({
  scaleFactor?: number,  // Display scale factor (auto-detected if omitted)
  logEvents?: boolean,   // Log events to console (default: true)
  logger?: OverlayLogger, // Custom logger
});

// Lifecycle
overlay.start(): void
overlay.stop(): void
overlay.isStarted(): boolean
overlay.isConnected(): boolean

// Window management
overlay.createWindow(options: OsrWindowOptions): OsrWindow
overlay.removeWindow(windowOrId: OsrWindow | number): void
overlay.getWindow(id: number): OsrWindow | undefined
overlay.getWindows(): OsrWindow[]

// Injection
overlay.inject(target: WindowInfo): { success: boolean; error?: string }

// Commands
overlay.setInputInterception(enabled: boolean): void
overlay.setCursor(cursor: string): void  // e.g., 'IDC_ARROW', 'IDC_HAND'
overlay.setFpsVisible(visible: boolean): void
overlay.setHotkeys(hotkeys: Hotkey[]): void
overlay.sendCommand(command: OverlayCommand): void

// Events
overlay.on(event, listener): () => void  // Returns unsubscribe function
overlay.off(event, listener): void
overlay.onAny(listener): () => void

// Scale factor
overlay.getScaleFactor(): number
overlay.setScaleFactor(factor: number): void
```

### `OsrWindowOptions`

Options for creating an overlay window:

```typescript
interface OsrWindowOptions {
  name: string;           // Window name (for debugging)
  url: string;            // URL to load
  x: number;              // X position (logical pixels)
  y: number;              // Y position (logical pixels)
  width: number;          // Width (logical pixels)
  height: number;         // Height (logical pixels)
  caption?: {             // Draggable titlebar region
    left?: number;
    right?: number;
    top?: number;
    height: number;
  };
  transparent?: boolean;  // Default: false
  resizable?: boolean;    // In-game resize (default: true)
  minWidth?: number;      // Default: 100
  minHeight?: number;     // Default: 100
  maxWidth?: number;      // Default: display width
  maxHeight?: number;     // Default: display height
  dragBorderWidth?: number; // Resize border (default: 10)
  frameRate?: number;     // OSR frame rate (default: 60)
}
```

### `OsrWindow`

Managed off-screen rendered window.

```typescript
const window = overlay.createWindow(options);

window.id: number                  // Electron BrowserWindow ID
window.name: string                // Window name
window.browserWindow: BrowserWindow  // Underlying BrowserWindow
window.options: OsrWindowOptions   // Creation options
window.rect: Rect                  // Current bounds (physical pixels)

window.resize(width: number, height: number): void
window.setScaleFactor(factor: number): void
window.getScaleFactor(): number
window.isDestroyed(): boolean
window.destroy(): void
```

### `WindowPicker`

Utility for finding injection targets.

```typescript
const picker = new WindowPicker();

// Get all windows
picker.getWindows(options?: WindowPickerOptions): EnrichedWindowInfo[]
picker.getGameWindows(): EnrichedWindowInfo[]  // Shorthand for gamesOnly

// Find specific windows
picker.findByTitle(title: string, exact?: boolean): EnrichedWindowInfo | undefined
picker.findByProcessId(pid: number): EnrichedWindowInfo[]
picker.findByProcessName(name: string): EnrichedWindowInfo[]

// Cache control
picker.clearCache(): void
picker.setCacheDuration(ms: number): void
```

### Events

Available events from `OverlayManager`:

| Event | Payload | Description |
|-------|---------|-------------|
| `connected` | `{ processId, hwnd? }` | DLL connected to overlay system |
| `disconnected` | `{ processId? }` | DLL disconnected |
| `graphics.setup` | `{ hwnd, width, height }` | Graphics window initialized |
| `window.bounds` | `{ windowId, rect }` | Window dragged/resized |
| `window.resize` | `{ windowId, width, height }` | Window size changed |
| `graphics.fps` | `{ fps }` | FPS counter update |
| `hotkey` | `{ name, keyCode }` | Registered hotkey pressed |
| `log` | `{ level, target, message }` | Log from DLL |
| `input.raw` | `InputEventPayload` | Raw input event (for debugging) |

---

## Low-Level API

For advanced use cases, you can use the native API directly:

```javascript
const {
  Overlay,
  getTopWindows,
  injectProcess,
  injectProcessWithHelper,
  getBinaryPaths
} = require('@hamstu/electron-game-overlay');

// List all injectable windows
const windows = getTopWindows(false);
console.log('Found windows:', windows);

// Find the game you want to overlay
const gameWindow = windows.find(w => w.title.includes('My Game'));

// Create and start the overlay system
const overlay = new Overlay();
overlay.start();

// Set up event handling
overlay.setEventCallback((event, payload) => {
  console.log('Overlay event:', event, payload);

  if (event === 'game.input') {
    // Forward input to your Electron window
  }
});

// Inject into the game (automatically uses bundled DLLs)
const result = injectProcess(gameWindow);
console.log('Injection result:', result);

// Add an overlay window (typically an offscreen Electron BrowserWindow)
overlay.addWindow(windowId, {
  name: 'MyOverlay',
  nativeHandle: window.getNativeWindowHandle().readUInt32LE(0),
  transparent: true,
  resizable: true,
  maxWidth: 1920,
  maxHeight: 1080,
  minWidth: 100,
  minHeight: 100,
  rect: { x: 100, y: 100, width: 400, height: 300 }
});

// Send frame buffer from Electron's offscreen rendering
window.webContents.on('paint', (event, dirty, image) => {
  overlay.sendFrameBuffer(
    windowId,
    image.getBitmap(),
    image.getSize().width,
    image.getSize().height
  );
});
```

### Native API Reference

#### Functions

##### `getTopWindows(includeMinimized?: boolean): WindowInfo[]`

Get a list of top-level windows suitable for injection.

```typescript
interface WindowInfo {
  hwnd: number;
  processId: number;
  threadId: number;
  title: string;
  is64Bit: boolean;
}
```

##### `injectProcess(window: WindowInfo, dllPath?: string): InjectResult`

Inject the overlay DLL into a process. If no `dllPath` is provided, automatically uses the bundled DLL for the target architecture.

```typescript
interface InjectResult {
  success: boolean;
  processId: number;
  is64Bit: boolean;
  error?: string;
}
```

##### `injectProcessWithHelper(window: WindowInfo, dllPath?: string, helperPath?: string): InjectResult`

Inject using a helper executable. Required for cross-architecture injection (e.g., 64-bit Electron injecting into 32-bit game). If paths are not provided, automatically uses bundled binaries.

##### `getBinaryPaths(): BinaryPaths | null`

Get paths to all bundled binaries. Useful for diagnostics.

```typescript
interface BinaryPaths {
  binDir: string;
  overlayX64Dll: string;
  overlayX86Dll: string;
  injectorX64Exe: string;
  injectorX86Exe: string;
}
```

##### `getOverlayDllPath(is64Bit: boolean): string | null`

Get path to the appropriate overlay DLL for the target architecture.

##### `getInjectorHelperPath(is64Bit: boolean): string | null`

Get path to the appropriate injector helper for the target architecture.

#### Overlay Class

##### `new Overlay()`

Create a new Overlay instance.

##### `overlay.start(): void`

Start the overlay IPC system.

##### `overlay.stop(): void`

Stop the overlay system.

##### `overlay.setEventCallback(callback: (event: string, payload: object) => void): void`

Set the callback for overlay events.

##### `overlay.setScaleFactor(factor: number): void`

Set the display scale factor for coordinate conversion.

##### `overlay.addWindow(windowId: number, details: WindowDetails): void`

Add an overlay window.

```typescript
interface WindowDetails {
  name: string;
  nativeHandle: number;
  transparent: boolean;
  resizable: boolean;
  maxWidth: number;
  maxHeight: number;
  minWidth: number;
  minHeight: number;
  dragBorderWidth?: number;
  rect: { x: number; y: number; width: number; height: number };
  caption?: { left: number; right: number; top: number; height: number };
}
```

##### `overlay.closeWindow(windowId: number): void`

Close an overlay window.

##### `overlay.sendFrameBuffer(windowId: number, buffer: Buffer, width: number, height: number): void`

Send frame buffer data for a window.

##### `overlay.sendWindowBounds(windowId: number, rect: Rect): void`

Update window bounds.

##### `overlay.sendCommand(command: object): void`

Send a command to the overlay.

```javascript
// Change cursor
overlay.sendCommand({ command: 'cursor', cursor: 'IDC_HAND' });

// Toggle FPS display
overlay.sendCommand({ command: 'fps', showfps: true, position: 1 });

// Toggle input interception
overlay.sendCommand({ command: 'input.intercept', intercept: true });
```

##### `overlay.translateInputEvent(event: object): object`

Translate a Windows input event to Electron format.

### Native Events

| Event | Description |
|-------|-------------|
| `game.process` | DLL connected to the overlay system |
| `game.input` | Input event from the game (mouse, keyboard) |
| `window.bounds` | Window bounds updated (drag/resize) |
| `window.resize` | Window size changed |
| `graphics.fps` | FPS counter update |
| `log` | Log message from the DLL |

---

## Troubleshooting

### Antivirus False Positives

DLL injection is a technique also used by malware, so antivirus software may flag the overlay DLL. To resolve:

1. Add an exception for the `@hamstu/electron-game-overlay` package directory
2. Add an exception for your application's installation directory
3. Consider code signing your application for production releases

### Injection Fails

1. Ensure the game is running with the same or lower privileges than your Electron app
2. Some games with anti-cheat may block DLL injection
3. Check that the correct architecture (32-bit vs 64-bit) is being used

### Overlay Not Visible

1. Verify the game is using a supported DirectX version (9/10/11/12)
2. Check that frame buffers are being sent correctly
3. Ensure the overlay window bounds are within the game's render area

## Architecture

```
┌────────────────────────────────────────────────────────────┐
│              Electron Application                          │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  Off-Screen Rendered Windows (OSR)                   │  │
│  │  - Electron renders to offscreen buffers             │  │
│  │  - Paint events capture frame data                   │  │
│  └──────────────────────┬───────────────────────────────┘  │
│                         │                                  │
│  ┌──────────────────────▼───────────────────────────────┐  │
│  │  @hamstu/electron-game-overlay (Native Node Addon)   │  │
│  │  - IPC Host managing connections from injected DLLs  │  │
│  │  - Shared memory creation for frame buffers          │  │
│  │  - Event dispatch to JavaScript                      │  │
│  └──────────────────────┬───────────────────────────────┘  │
└─────────────────────────┼──────────────────────────────────┘
                          │
        Named Shared Memory + Windows Messages
                          │
┌─────────────────────────▼──────────────────────────────────┐
│              Target Game Process                           │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  overlay.dll (Injected)                              │  │
│  │  - Hooks DXGI Present / D3D9 EndScene                │  │
│  │  - Reads frame buffers from shared memory            │  │
│  │  - Renders overlay textures via DirectX              │  │
│  │  - Captures and forwards input events                │  │
│  └──────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────┘
```

## License

MIT