Steamworks-ffi-node: бібліотека для роботи зі Steamworks SDK для ігор побудованих на JS фреймворках

Мене звати Хутак Артур, і я хочу розповісти про свою опенсорсну бібліотеку для роботи зі Steamworks SDK для ігор та додатків побудованих на Javascript фреймворках типу Electron, NW.js та інших.

З чого все почалося

Я розробляв додаток на Electron для релізу у Steam. Все йшло добре до моменту, коли знадобилося додати декілька Steam-інтеграцій: досягнення, таблиці лідерів, хмарне збереження та підтримку Steam Overlay. Перше, що знайщов у Google — це greenworks та steamworks.js. Я спробував обидві бібліотеки, але обидві розчарували. Greenworks — через відсутність деяких Steam-інтеграцій, необхідність компілювати потрібну версію бібліотеки та заточеність на NW.js. Steamworks.js — через низьку підтримку, відсутність документації та проблеми зі Steam Overlay, який працював тільки на Windows.

З розвитком ШІ агентів, наважився створити власну бібліотеку для роботи зі Steamworks SDK. Так зʼявилась бібліотека steamworks-ffi-node — Node.js обгортка для Steamworks SDK, яка використовує FFI замість нативної компіляції C++. У цій статті я розповім про технічні рішення, порівняю підходи інших бібліотек та поділюся тим, що дізнався під час розробки.

Проблема: Steam SDK і JavaScript — погано сумісні речі

Valve надає Steamworks SDK як набір C++ заголовків та динамічних бібліотек (steam_api64.dll, libsteam_api.so, libsteam_api.dylib). Щоб викликати ці функції з Node.js, класично існують два підходи:

  1. Написати нативний Node.js addon на C++ (Node-API / NAN) — компілюється під кожну платформу і версію Node окремо.
  2. Використати FFI (Foreign Function Interface) — викликати функції з бібліотеки напряму з JavaScript, без компіляції.

Перший підхід обрали greenworks і steamworks.js, а другий — steamworks-ffi-node.

Огляд популярних бібліотек

greenworks

greenworks — найстаріша бібліотека в цьому просторі, розроблена компанією Greenheart Games для їхньої гри Game Dev Tycoon. Пізніше відкрита як open-source.

Підхід: Нативний C++ addon, написаний через NAN (Native Abstractions for Node.js).

Проблеми, з якими я зіткнувся:

  • Для встановлення потрібно завантажити декілька зборок на Windows та macOS/Linux.
  • Підтримується на «best-effort basis» — активна розробка не є пріоритетом для мейнтейнера.
  • Немає TypeScript типізації.
  • Підтримує Steamworks SDK v1.62, але частина API залишається непокритою.

Останній реліз: v0.22.0 (вересень 2025), але кількість відкритих issues — 61, pull requests — 4.

steamworks.js

steamworks.js — сучасніша альтернатива, побудована на базі Rust через NAPI-RS.

Підхід: Нативний addon через Rust + NAPI-RS. Прекомпільовані бінарники для основних платформ.

Що добре:

  • Немає потреби компілювати самостійно (є прекомпільовані бінарники).
  • TypeScript типізація є.
  • Сучасніший API.

Обмеження, з якими я зіткнувся:

  • Бібліотека привʼязана до конкретних версій Node.js через NAPI ABI.
  • Покриття API суттєво обмежене — підтримуються досягнення, базові stats, leaderboards, friends, але немає повноцінного Input API, Matchmaking, Networking Sockets, Cloud Storage batch operations, Screenshots тощо.
  • На разі активність мейнтейнера відсутня

Останній реліз: v0.4.0 (два роки тому згідно npmjs), кількість відкритих issues — 52, pull requests — 13.

Технічне рішення: чому FFI, а не нативний addon

Як працює koffi

koffi — це FFI-бібліотека для Node.js, яка дозволяє викликати функції з нативних динамічних бібліотек напряму з JavaScript, без написання C++ коду.

Приклад:

Переваги підходу

Відсутність компіляції при встановленні

greenworks і навіть steamworks.js у деяких сценаріях вимагають наявності збірного середовища. FFI-підхід означає, що бібліотека — це чистий JavaScript/TypeScript, що встановлюється як звичайний npm-пакет.

Незалежність від версії Node.js

Нативні аддони (NAN, NAPI-RS) прив’язані до ABI конкретної версії V8/Node. Це означає, що при оновленні Electron з v28 до v39 доводиться перекомпільовувати або чекати нового релізу бібліотеки.

Koffi FFI — це JS-код, що викликає .so/.dll напряму. ABI бінарної бібліотеки Steam не змінюється. Оновлення Electron не ламає інтеграцію.

Легкий контрибьютинг

Весь код бібліотеки написаний на TypeScript, тому окремо компілювати не потрібно. Приклад щоб додати нову функцію зі Steamworks SDK:

Складні технічні моменти

Проблема struct return ABI на Linux

Це, мабуть, найцікавіший баг, який я виправив у версії 0.9.3. Steam Input API повертає структури безпосередньо з функцій.

На Windows (MSVC x64 ABI) маленькі структури повертаються через регістри RAX/RDX. Однак при початковій реалізації я використав паттерн void* output buffer.

На Linux (System V x86_64 ABI) маленькі структури до 16 байт повертаються в парі регістрів RAX:RDX. Великі структури (як InputMotionData_t — 40 байт) повертаються через прихований вказівник, який передається першим аргументом.

Мій неправильний код передавав buffer як другий аргумент — функція писала 40 байт даних прямо у ISteamInput, що призводило до memory corruption і segfault.

Правильним рішенням було оголосити структури через Koffi і використати правильний return type:

Koffi автоматично обирає правильний calling convention залежно від платформи та розміру структури.

Native Steam Overlay для Electron

Стандартний Steam Overlay (Shift+Tab) не працює в Electron-іграх «з коробки» — Steam не знає, як впровадити свій рендеринг у Chromium.

Я реалізував нативні C++ модулі для кожної платформи, які перехоплюють рендеринг:

  • macOS: Metal rendering через MTKView і CAMetalLayer
  • Windows: OpenGL через Win32 window hooks
  • Linux/SteamOS: OpenGL 3.3 з GLX, але протестував тільки на Steam Deck Desktop Mode

Це єдина частина бібліотеки, що вимагає нативної компіляції, але готові бінарники я поширюю через npm пакет, тому для інтеграції зі Steam Overlay достатньо просто встановити пакет.

Основний челендж було засінкати Electron вікно з нативним, для цього дійшов до підходу як на cхемі:

steamworks-ffi-node - Steam Overlay architecture

Архітектура бібліотеки

Кожен менеджер відповідає за один розділ Steamworks API. Це дозволяє легко додавати нові API без зміни існуючого коду.

steamworks-ffi-node - architecture

Приклади використання

У прикладі можно подивитись як працювати з досягненнями, статистикою, таблицею лідерів та хмарним збереженням Steam.

Ігри які вже використовують бібліотеку

Бібліотека вже використовується в декількох опублікованих Steam іграх:

Висновки

Я отримав чудовий досвід від роботи за FFI підходом та Steamworks SDK в цілому та в процесі вирішення різних челенджів як зі Steam Input та Steam Overlay.

Якщо ви розробляєте гру на Electron або або на іншому фреймворці і потребуєте Steam-інтеграції — зараз у вас є вибір між трьома підходами:

  • greenworks — якщо вам потрібна тільки базова функціональність і ви не проти залежності від C++ компілятора. Має найбільшу кількість зірок завдяки своєму віку, але активно не підтримується.
  • steamworks.js — якщо вас влаштовує обмежене API і ви хочете Rust-реалізацію з NAPI.
  • steamworks-ffi-node — якщо потрібне більш повне покриття API, відсутність компіляції, TypeScript, та активна розробка від мене.

Якщо стаття була корисною — поставте зірку на GitHub. :)
Питання та пропозиції можете залишити тут або через GitHub Issues.

Підписуйтеся на Telegram-канал @gamedev_dou, щоб не пропустити найважливіші статті і новини

👍ПодобаєтьсяСподобалось5
До обраногоВ обраному3
LinkedIn


Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

совет один. никогда не делайте игру для стима на электроне. аминь.

Підписатись на коментарі