steamworks-ffi-node 0.6.10 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,6 +41,8 @@ A TypeScript/JavaScript wrapper for the Steamworks SDK using Koffi FFI, designed
41
41
 
42
42
  > 🎉 **NEW: Workshop API** - 29 functions for complete Steam Workshop/UGC integration! [See Documentation](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/WORKSHOP_MANAGER.md)
43
43
 
44
+ > 🎉 **NEW: Input API** - 35+ functions for complete Steam Input (controller) support! [See Documentation](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/INPUT_MANAGER.md) ⚠️ Tested with virtual gamepad only
45
+
44
46
  ## Features
45
47
 
46
48
  - **Core API**: Essential Steam application functions
@@ -94,6 +96,12 @@ A TypeScript/JavaScript wrapper for the Steamworks SDK using Koffi FFI, designed
94
96
  - ✅ Query operations (text search, browse, filter Workshop content)
95
97
  - ✅ Item creation & update (create, upload, manage your Workshop items)
96
98
  - ✅ Voting & favorites (vote on items, manage favorites)
99
+ - **Input API**: Complete Steam Input (controller) support (35+ functions) ⚠️ _Tested with virtual gamepad only_
100
+ - ✅ Controller detection (Xbox, PlayStation, Switch, Steam Controller, Steam Deck)
101
+ - ✅ Action sets and layers (menu controls, gameplay controls, etc.)
102
+ - ✅ Digital actions (buttons) and analog actions (sticks/triggers)
103
+ - ✅ Motion data (gyro, accelerometer for supported controllers)
104
+ - ✅ Haptics (vibration, LED control for DualShock/DualSense)
97
105
  - **Steamworks Integration**: Direct FFI calls to Steamworks C++ SDK
98
106
  - **Cross-Platform**: Windows, macOS, and Linux support
99
107
  - **Easy Setup**: Simple installation with clear SDK setup guide
@@ -113,23 +121,26 @@ npm install steamworks-ffi-node
113
121
  ### Setup
114
122
 
115
123
  1. **Download Steamworks SDK** (required separately due to licensing):
124
+
116
125
  - Visit [Steamworks Partner site](https://partner.steamgames.com/)
117
126
  - Download the latest Steamworks SDK
118
127
  - Extract and copy `redistributable_bin` folder to your project
119
128
  - See [STEAMWORKS_SDK_SETUP.md](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/STEAMWORKS_SDK_SETUP.md) for detailed instructions
120
129
 
121
130
  2. **Create `steam_appid.txt` (optional)** in your project root:
131
+
122
132
  ```bash
123
133
  echo "480" > steam_appid.txt # Use 480 for testing, or your Steam App ID
124
134
  ```
125
- *Note: You can skip this file and pass the App ID directly to `steam.init(appId)` instead*
135
+
136
+ _Note: You can skip this file and pass the App ID directly to `steam.init(appId)` instead_
126
137
 
127
138
  3. **Make sure Steam is running** and you're logged in
128
139
 
129
140
  ### Basic Usage
130
141
 
131
142
  ```typescript
132
- import SteamworksSDK from 'steamworks-ffi-node';
143
+ import SteamworksSDK from "steamworks-ffi-node";
133
144
 
134
145
  // Helper to auto-start callback polling
135
146
  function startCallbackPolling(steam: SteamworksSDK, interval: number = 1000) {
@@ -145,48 +156,50 @@ const initialized = steam.init({ appId: 480 }); // Your Steam App ID
145
156
  if (initialized) {
146
157
  // Start callback polling automatically (required for async operations)
147
158
  const callbackInterval = startCallbackPolling(steam, 1000);
148
-
159
+
149
160
  // Get current Steam language for localization
150
161
  const language = steam.getCurrentGameLanguage();
151
- console.log('Steam language:', language); // e.g., 'english', 'french', 'german'
152
-
162
+ console.log("Steam language:", language); // e.g., 'english', 'french', 'german'
163
+
153
164
  // Get achievements from Steam servers
154
165
  const achievements = await steam.achievements.getAllAchievements();
155
- console.log('Steam achievements:', achievements);
156
-
166
+ console.log("Steam achievements:", achievements);
167
+
157
168
  // Unlock achievement (permanent in Steam!)
158
- await steam.achievements.unlockAchievement('ACH_WIN_ONE_GAME');
159
-
169
+ await steam.achievements.unlockAchievement("ACH_WIN_ONE_GAME");
170
+
160
171
  // Check unlock status from Steam
161
- const isUnlocked = await steam.achievements.isAchievementUnlocked('ACH_WIN_ONE_GAME');
162
- console.log('Achievement unlocked:', isUnlocked);
163
-
172
+ const isUnlocked = await steam.achievements.isAchievementUnlocked(
173
+ "ACH_WIN_ONE_GAME"
174
+ );
175
+ console.log("Achievement unlocked:", isUnlocked);
176
+
164
177
  // Track user statistics
165
- const kills = await steam.stats.getStatInt('total_kills') || 0;
166
- await steam.stats.setStatInt('total_kills', kills + 1);
167
-
178
+ const kills = (await steam.stats.getStatInt("total_kills")) || 0;
179
+ await steam.stats.setStatInt("total_kills", kills + 1);
180
+
168
181
  // Get global statistics
169
182
  await steam.stats.requestGlobalStats(7);
170
- await new Promise(resolve => setTimeout(resolve, 2000));
183
+ await new Promise((resolve) => setTimeout(resolve, 2000));
171
184
  steam.runCallbacks();
172
- const globalKills = await steam.stats.getGlobalStatInt('global.total_kills');
173
- console.log('Total kills worldwide:', globalKills);
174
-
185
+ const globalKills = await steam.stats.getGlobalStatInt("global.total_kills");
186
+ console.log("Total kills worldwide:", globalKills);
187
+
175
188
  // Work with leaderboards
176
189
  const leaderboard = await steam.leaderboards.findOrCreateLeaderboard(
177
- 'HighScores',
190
+ "HighScores",
178
191
  1, // Descending (higher is better)
179
- 0 // Numeric display
192
+ 0 // Numeric display
180
193
  );
181
-
194
+
182
195
  if (leaderboard) {
183
196
  // Upload score
184
197
  await steam.leaderboards.uploadLeaderboardScore(
185
198
  leaderboard.handle,
186
199
  1000,
187
- 1 // Keep best score
200
+ 1 // Keep best score
188
201
  );
189
-
202
+
190
203
  // Download top 10 scores
191
204
  const topScores = await steam.leaderboards.downloadLeaderboardEntries(
192
205
  leaderboard.handle,
@@ -194,36 +207,38 @@ if (initialized) {
194
207
  0,
195
208
  9
196
209
  );
197
- console.log('Top 10 scores:', topScores);
210
+ console.log("Top 10 scores:", topScores);
198
211
  }
199
-
212
+
200
213
  // Access friends and social features
201
214
  const personaName = steam.friends.getPersonaName();
202
215
  const friendCount = steam.friends.getFriendCount(4); // All friends
203
216
  console.log(`${personaName} has ${friendCount} friends`);
204
-
217
+
205
218
  // Get all friends with details
206
219
  const allFriends = steam.friends.getAllFriends(4); // All friends
207
- allFriends.slice(0, 5).forEach(friend => {
220
+ allFriends.slice(0, 5).forEach((friend) => {
208
221
  const name = steam.friends.getFriendPersonaName(friend.steamId);
209
222
  const state = steam.friends.getFriendPersonaState(friend.steamId);
210
223
  const level = steam.friends.getFriendSteamLevel(friend.steamId);
211
224
  console.log(`${name}: Level ${level}, Status: ${state}`);
212
-
225
+
213
226
  // Get avatar handles
214
227
  const smallAvatar = steam.friends.getSmallFriendAvatar(friend.steamId);
215
228
  const mediumAvatar = steam.friends.getMediumFriendAvatar(friend.steamId);
216
229
  if (smallAvatar > 0) {
217
- console.log(` Avatar handles: small=${smallAvatar}, medium=${mediumAvatar}`);
230
+ console.log(
231
+ ` Avatar handles: small=${smallAvatar}, medium=${mediumAvatar}`
232
+ );
218
233
  }
219
-
234
+
220
235
  // Check if playing a game
221
236
  const gameInfo = steam.friends.getFriendGamePlayed(friend.steamId);
222
237
  if (gameInfo) {
223
238
  console.log(` Playing: App ${gameInfo.gameId}`);
224
239
  }
225
240
  });
226
-
241
+
227
242
  // Check friend groups (tags)
228
243
  const groupCount = steam.friends.getFriendsGroupCount();
229
244
  if (groupCount > 0) {
@@ -232,7 +247,7 @@ if (initialized) {
232
247
  const members = steam.friends.getFriendsGroupMembersList(groupId);
233
248
  console.log(`Group "${groupName}" has ${members.length} members`);
234
249
  }
235
-
250
+
236
251
  // Check recently played with
237
252
  const coplayCount = steam.friends.getCoplayFriendCount();
238
253
  if (coplayCount > 0) {
@@ -241,107 +256,123 @@ if (initialized) {
241
256
  const coplayTime = steam.friends.getFriendCoplayTime(recentPlayer);
242
257
  console.log(`Recently played with ${playerName}`);
243
258
  }
244
-
259
+
245
260
  // Set rich presence for custom status
246
- steam.richPresence.setRichPresence('status', 'In Main Menu');
247
- steam.richPresence.setRichPresence('connect', '+connect server:27015');
248
-
261
+ steam.richPresence.setRichPresence("status", "In Main Menu");
262
+ steam.richPresence.setRichPresence("connect", "+connect server:27015");
263
+
249
264
  // Open Steam overlay
250
- steam.overlay.activateGameOverlay('Friends'); // Open friends list
251
- steam.overlay.activateGameOverlayToWebPage('https://example.com/wiki'); // Open wiki
252
-
265
+ steam.overlay.activateGameOverlay("Friends"); // Open friends list
266
+ steam.overlay.activateGameOverlayToWebPage("https://example.com/wiki"); // Open wiki
267
+
253
268
  // Steam Cloud storage operations
254
- const saveData = { level: 5, score: 1000, inventory: ['sword', 'shield'] };
269
+ const saveData = { level: 5, score: 1000, inventory: ["sword", "shield"] };
255
270
  const buffer = Buffer.from(JSON.stringify(saveData));
256
-
271
+
257
272
  // Write save file to Steam Cloud
258
- const written = steam.cloud.fileWrite('savegame.json', buffer);
273
+ const written = steam.cloud.fileWrite("savegame.json", buffer);
259
274
  if (written) {
260
- console.log('✅ Save uploaded to Steam Cloud');
275
+ console.log("✅ Save uploaded to Steam Cloud");
261
276
  }
262
-
277
+
263
278
  // Check cloud quota
264
279
  const quota = steam.cloud.getQuota();
265
- console.log(`Cloud storage: ${quota.usedBytes}/${quota.totalBytes} bytes (${quota.percentUsed.toFixed(2)}%)`);
266
-
280
+ console.log(
281
+ `Cloud storage: ${quota.usedBytes}/${
282
+ quota.totalBytes
283
+ } bytes (${quota.percentUsed.toFixed(2)}%)`
284
+ );
285
+
267
286
  // Read save file from Steam Cloud
268
- if (steam.cloud.fileExists('savegame.json')) {
269
- const result = steam.cloud.fileRead('savegame.json');
287
+ if (steam.cloud.fileExists("savegame.json")) {
288
+ const result = steam.cloud.fileRead("savegame.json");
270
289
  if (result.success && result.data) {
271
290
  const loadedSave = JSON.parse(result.data.toString());
272
- console.log(`Loaded save: Level ${loadedSave.level}, Score ${loadedSave.score}`);
291
+ console.log(
292
+ `Loaded save: Level ${loadedSave.level}, Score ${loadedSave.score}`
293
+ );
273
294
  }
274
295
  }
275
-
296
+
276
297
  // List all cloud files
277
298
  const cloudFiles = steam.cloud.getAllFiles();
278
299
  console.log(`Steam Cloud contains ${cloudFiles.length} files:`);
279
- cloudFiles.forEach(file => {
300
+ cloudFiles.forEach((file) => {
280
301
  const kb = (file.size / 1024).toFixed(2);
281
- const status = file.persisted ? '☁️' : '';
302
+ const status = file.persisted ? "☁️" : "";
282
303
  console.log(`${status} ${file.name} - ${kb} KB`);
283
304
  });
284
-
305
+
285
306
  // Steam Workshop operations
286
307
  // Subscribe to a Workshop item
287
308
  const subscribeResult = await steam.workshop.subscribeItem(123456789n);
288
309
  if (subscribeResult.success) {
289
- console.log('✅ Subscribed to Workshop item');
310
+ console.log("✅ Subscribed to Workshop item");
290
311
  }
291
-
312
+
292
313
  // Get all subscribed items
293
314
  const subscribedItems = steam.workshop.getSubscribedItems();
294
315
  console.log(`Subscribed to ${subscribedItems.length} Workshop items`);
295
-
316
+
296
317
  // Query Workshop items with text search
297
318
  const query = steam.workshop.createQueryAllUGCRequest(
298
- 11, // RankedByTextSearch - for text search queries
299
- 0, // Items
300
- 480, // Creator App ID
301
- 480, // Consumer App ID
302
- 1 // Page 1
319
+ 11, // RankedByTextSearch - for text search queries
320
+ 0, // Items
321
+ 480, // Creator App ID
322
+ 480, // Consumer App ID
323
+ 1 // Page 1
303
324
  );
304
-
325
+
305
326
  if (query) {
306
327
  // Set search text to filter results
307
- steam.workshop.setSearchText(query, 'map');
308
-
328
+ steam.workshop.setSearchText(query, "map");
329
+
309
330
  const queryResult = await steam.workshop.sendQueryUGCRequest(query);
310
331
  if (queryResult) {
311
- console.log(`Found ${queryResult.numResults} Workshop items matching "map"`);
312
-
332
+ console.log(
333
+ `Found ${queryResult.numResults} Workshop items matching "map"`
334
+ );
335
+
313
336
  // Get details for each item
314
337
  for (let i = 0; i < queryResult.numResults; i++) {
315
338
  const details = steam.workshop.getQueryUGCResult(query, i);
316
339
  if (details) {
317
340
  console.log(`📦 ${details.title} by ${details.steamIDOwner}`);
318
- console.log(` Score: ${details.score}, Downloads: ${details.numUniqueSubscriptions}`);
341
+ console.log(
342
+ ` Score: ${details.score}, Downloads: ${details.numUniqueSubscriptions}`
343
+ );
319
344
  }
320
345
  }
321
346
  }
322
347
  steam.workshop.releaseQueryUGCRequest(query);
323
348
  }
324
-
349
+
325
350
  // Check download progress for subscribed items
326
- subscribedItems.forEach(itemId => {
351
+ subscribedItems.forEach((itemId) => {
327
352
  const state = steam.workshop.getItemState(itemId);
328
353
  const stateFlags = [];
329
- if (state & 1) stateFlags.push('Subscribed');
330
- if (state & 4) stateFlags.push('Needs Update');
331
- if (state & 8) stateFlags.push('Installed');
332
- if (state & 16) stateFlags.push('Downloading');
333
-
334
- console.log(`Item ${itemId}: ${stateFlags.join(', ')}`);
335
-
336
- if (state & 16) { // If downloading
354
+ if (state & 1) stateFlags.push("Subscribed");
355
+ if (state & 4) stateFlags.push("Needs Update");
356
+ if (state & 8) stateFlags.push("Installed");
357
+ if (state & 16) stateFlags.push("Downloading");
358
+
359
+ console.log(`Item ${itemId}: ${stateFlags.join(", ")}`);
360
+
361
+ if (state & 16) {
362
+ // If downloading
337
363
  const progress = steam.workshop.getItemDownloadInfo(itemId);
338
364
  if (progress) {
339
- const percent = (progress.downloaded / progress.total * 100).toFixed(1);
340
- console.log(` Download: ${percent}% (${progress.downloaded}/${progress.total} bytes)`);
365
+ const percent = ((progress.downloaded / progress.total) * 100).toFixed(
366
+ 1
367
+ );
368
+ console.log(
369
+ ` Download: ${percent}% (${progress.downloaded}/${progress.total} bytes)`
370
+ );
341
371
  }
342
372
  }
343
-
344
- if (state & 8) { // If installed
373
+
374
+ if (state & 8) {
375
+ // If installed
345
376
  const info = steam.workshop.getItemInstallInfo(itemId);
346
377
  if (info.success) {
347
378
  console.log(` Installed at: ${info.folder}`);
@@ -359,13 +390,13 @@ steam.shutdown();
359
390
 
360
391
  ```javascript
361
392
  // Option 1: ESM Named import
362
- import { SteamworksSDK } from 'steamworks-ffi-node';
393
+ import { SteamworksSDK } from "steamworks-ffi-node";
363
394
 
364
395
  // Option 2: CommonJs named import (recommended - no .default needed)
365
- const { SteamworksSDK } = require('steamworks-ffi-node');
396
+ const { SteamworksSDK } = require("steamworks-ffi-node");
366
397
 
367
398
  // Option 3: CommonJs default named import (also works)
368
- const SteamworksSDK = require('steamworks-ffi-node').default;
399
+ const SteamworksSDK = require("steamworks-ffi-node").default;
369
400
 
370
401
  // Helper to auto-start callback polling
371
402
  function startCallbackPolling(steam, interval = 1000) {
@@ -376,24 +407,24 @@ function startCallbackPolling(steam, interval = 1000) {
376
407
 
377
408
  async function example() {
378
409
  const steam = SteamworksSDK.getInstance();
379
-
410
+
380
411
  if (steam.init({ appId: 480 })) {
381
412
  // Start callback polling automatically
382
413
  const callbackInterval = startCallbackPolling(steam, 1000);
383
-
414
+
384
415
  const achievements = await steam.achievements.getAllAchievements();
385
416
  console.log(`Found ${achievements.length} achievements`);
386
-
417
+
387
418
  // Unlock first locked achievement
388
- const locked = achievements.find(a => !a.unlocked);
419
+ const locked = achievements.find((a) => !a.unlocked);
389
420
  if (locked) {
390
421
  await steam.achievements.unlockAchievement(locked.apiName);
391
422
  }
392
-
423
+
393
424
  // Cleanup
394
425
  clearInterval(callbackInterval);
395
426
  }
396
-
427
+
397
428
  steam.shutdown();
398
429
  }
399
430
 
@@ -403,6 +434,7 @@ example();
403
434
  ### Testing with Spacewar
404
435
 
405
436
  For immediate testing, use Spacewar (App ID 480):
437
+
406
438
  - Free Steam app for testing Steamworks features
407
439
  - Add to Steam library: `steam://install/480` or search "Spacewar" in Steam
408
440
  - Launch it once, then you can test with App ID 480
@@ -414,14 +446,16 @@ Complete documentation for all APIs is available in the [docs folder](https://gi
414
446
  ➡️ **[View Complete Documentation](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/README.md)**
415
447
 
416
448
  ### API Guides:
449
+
417
450
  - **[Achievement Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/ACHIEVEMENT_MANAGER.md)** - Complete achievement system (20 functions)
418
451
  - **[Stats Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/STATS_MANAGER.md)** - User and global statistics (14 functions)
419
452
  - **[Leaderboard Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/LEADERBOARD_MANAGER.md)** - Leaderboard operations (7 functions)
420
453
  - **[Friends Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/FRIENDS_MANAGER.md)** - Friends and social features (22 functions)
421
454
  - **[Rich Presence Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/RICH_PRESENCE_MANAGER.md)** - Custom status display and join functionality (6 functions)
422
455
  - **[Overlay Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/OVERLAY_MANAGER.md)** - Steam overlay control (7 functions)
423
- - **[Cloud Storage Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/CLOUD_STORAGE_MANAGER.md)** - Steam Cloud file operations (14 functions)
456
+ - **[Cloud Storage Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/CLOUD_MANAGER.md)** - Steam Cloud file operations (14 functions)
424
457
  - **[Workshop Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/WORKSHOP_MANAGER.md)** - Steam Workshop/UGC operations (29 functions)
458
+ - **[Input Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/INPUT_MANAGER.md)** - Steam Input controller support (35+ functions) ⚠️ _Virtual gamepad testing only_
425
459
 
426
460
  ## Steamworks Integration
427
461
 
@@ -437,11 +471,12 @@ const achievements = await steam.achievements.getAllAchievements();
437
471
  console.log(achievements); // Achievement data from your Steam app
438
472
 
439
473
  // Permanent achievement unlock in Steam
440
- await steam.achievements.unlockAchievement('YOUR_ACHIEVEMENT');
474
+ await steam.achievements.unlockAchievement("YOUR_ACHIEVEMENT");
441
475
  // ^ This shows up in Steam overlay and is saved permanently
442
476
  ```
443
477
 
444
478
  **What happens when you unlock an achievement:**
479
+
445
480
  - ✅ Steam overlay notification appears
446
481
  - ✅ Achievement saved to Steam servers permanently
447
482
  - ✅ Syncs across all devices
@@ -454,21 +489,21 @@ For Electron applications, use it in your main process:
454
489
 
455
490
  ```typescript
456
491
  // main.ts
457
- import { app } from 'electron';
458
- import SteamworksSDK from 'steamworks-ffi-node';
492
+ import { app } from "electron";
493
+ import SteamworksSDK from "steamworks-ffi-node";
459
494
 
460
495
  app.whenReady().then(() => {
461
496
  const steam = SteamworksSDK.getInstance();
462
-
497
+
463
498
  if (steam.init({ appId: YOUR_STEAM_APP_ID })) {
464
- console.log('Steam initialized in Electron!');
465
-
499
+ console.log("Steam initialized in Electron!");
500
+
466
501
  // Handle achievement unlocks from renderer process
467
502
  // via IPC if needed
468
503
  }
469
504
  });
470
505
 
471
- app.on('before-quit', () => {
506
+ app.on("before-quit", () => {
472
507
  const steam = SteamworksSDK.getInstance();
473
508
  steam.shutdown();
474
509
  });
@@ -482,42 +517,46 @@ The library searches for the SDK in standard locations within your Electron app
482
517
 
483
518
  ## Requirements
484
519
 
485
- - **Node.js**: 18+
520
+ - **Node.js**: 18+
486
521
  - **Steam Client**: Must be running and logged in
487
522
  - **Steam App ID**: Get yours at [Steamworks Partner](https://partner.steamgames.com/)
488
523
  - **steam_appid.txt**: Optional - create in your project root OR pass to `steam.init(appId)`
489
524
 
490
525
  ### Platform Support
526
+
491
527
  - ✅ **Windows**: steam_api64.dll / steam_api.dll
492
- - ✅ **macOS**: libsteam_api.dylib
528
+ - ✅ **macOS**: libsteam_api.dylib
493
529
  - ✅ **Linux**: libsteam_api.so
494
530
 
495
531
  **Steamworks SDK Version**: v1.62 (Latest)
496
532
 
497
- *Note: You must download and install the SDK redistributables separately as described in the Setup section above.*
533
+ _Note: You must download and install the SDK redistributables separately as described in the Setup section above._
498
534
 
499
535
  ## Troubleshooting
500
536
 
501
537
  ### "SteamAPI_Init failed"
538
+
502
539
  - ❌ Steam client not running → **Solution**: Start Steam and log in
503
540
  - ❌ No App ID specified → **Solution**: Create `steam_appid.txt` in project root OR pass App ID to `steam.init(appId)`
504
541
  - ❌ Invalid App ID → **Solution**: Use 480 for testing, or your registered App ID
505
542
 
506
543
  ### "Cannot find module 'steamworks-ffi-node'"
544
+
507
545
  - ❌ Package not installed → **Solution**: Run `npm install steamworks-ffi-node`
508
546
 
509
547
  ### Achievement operations not working
548
+
510
549
  - ❌ Not initialized → **Solution**: Call `steam.init({ appId })` first
511
550
  - ❌ No achievements configured → **Solution**: Configure achievements in Steamworks Partner site
512
551
  - ❌ Using Spacewar → **Note**: Spacewar may not have achievements, use your own App ID
513
552
 
514
553
  ### Electron-specific issues
554
+
515
555
  - ❌ Initialized in renderer → **Solution**: Only initialize in main process
516
556
  - ❌ Not cleaning up → **Solution**: Call `shutdown()` in `before-quit` event
517
557
  - ❌ "Steamworks SDK library not found" in packaged app → **Solution**: Include SDK redistributables in your build (see Electron Packaging section above)
518
558
  - ❌ Native module errors in packaged app → **Solution**: Ensure Steamworks SDK files are properly included in your app bundle
519
559
 
520
-
521
560
  ## How to Support This Project
522
561
 
523
562
  You can support the development of this library by wishlisting, subscribing, and purchasing the app **AFK Companion** on Steam: