steamworks-ffi-node 0.6.10 → 0.7.1
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 +157 -107
- package/dist/internal/SteamCloudManager.d.ts +86 -1
- package/dist/internal/SteamCloudManager.d.ts.map +1 -1
- package/dist/internal/SteamCloudManager.js +142 -0
- package/dist/internal/SteamCloudManager.js.map +1 -1
- package/dist/internal/SteamInputManager.d.ts +496 -0
- package/dist/internal/SteamInputManager.d.ts.map +1 -0
- package/dist/internal/SteamInputManager.js +993 -0
- package/dist/internal/SteamInputManager.js.map +1 -0
- package/dist/internal/SteamLibraryLoader.d.ts +61 -0
- package/dist/internal/SteamLibraryLoader.d.ts.map +1 -1
- package/dist/internal/SteamLibraryLoader.js +83 -0
- package/dist/internal/SteamLibraryLoader.js.map +1 -1
- package/dist/internal/SteamScreenshotManager.d.ts +327 -0
- package/dist/internal/SteamScreenshotManager.d.ts.map +1 -0
- package/dist/internal/SteamScreenshotManager.js +459 -0
- package/dist/internal/SteamScreenshotManager.js.map +1 -0
- package/dist/internal/SteamWorkshopManager.d.ts +24 -0
- package/dist/internal/SteamWorkshopManager.d.ts.map +1 -1
- package/dist/internal/SteamWorkshopManager.js +74 -0
- package/dist/internal/SteamWorkshopManager.js.map +1 -1
- package/dist/internal/callbackTypes/SteamCallbackIds.d.ts +2 -0
- package/dist/internal/callbackTypes/SteamCallbackIds.d.ts.map +1 -1
- package/dist/internal/callbackTypes/SteamCallbackIds.js +3 -1
- package/dist/internal/callbackTypes/SteamCallbackIds.js.map +1 -1
- package/dist/internal/callbackTypes/SteamCallbackTypes.d.ts +9 -0
- package/dist/internal/callbackTypes/SteamCallbackTypes.d.ts.map +1 -1
- package/dist/steam.d.ts +99 -0
- package/dist/steam.d.ts.map +1 -1
- package/dist/steam.js +9 -0
- package/dist/steam.js.map +1 -1
- package/dist/types/cloud.d.ts +11 -11
- package/dist/types/cloud.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/input.d.ts +252 -0
- package/dist/types/input.d.ts.map +1 -0
- package/dist/types/input.js +142 -0
- package/dist/types/input.js.map +1 -0
- package/dist/types/screenshots.d.ts +63 -0
- package/dist/types/screenshots.d.ts.map +1 -0
- package/dist/types/screenshots.js +44 -0
- package/dist/types/screenshots.js.map +1 -0
- package/dist/types/workshop.d.ts +8 -0
- package/dist/types/workshop.d.ts.map +1 -1
- package/dist/types/workshop.js.map +1 -1
- package/docs/README.md +49 -5
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -37,9 +37,13 @@ A TypeScript/JavaScript wrapper for the Steamworks SDK using Koffi FFI, designed
|
|
|
37
37
|
|
|
38
38
|
> 🎉 **NEW: Overlay API** - 7 functions for complete Steam overlay control! [See Documentation](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/OVERLAY_MANAGER.md)
|
|
39
39
|
|
|
40
|
-
> 🎉 **NEW: Cloud Storage API** -
|
|
40
|
+
> 🎉 **NEW: Cloud Storage API** - 17 functions for complete Steam Cloud (Remote Storage) integration including batch writes! [See Documentation](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/CLOUD_MANAGER.md)
|
|
41
41
|
|
|
42
|
-
> 🎉 **NEW: Workshop API** -
|
|
42
|
+
> 🎉 **NEW: Workshop API** - 30 functions for complete Steam Workshop/UGC integration including item deletion! [See Documentation](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/WORKSHOP_MANAGER.md)
|
|
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
|
+
|
|
46
|
+
> 🎉 **NEW: Screenshots API** - 9 functions for Steam Screenshots integration! [See Documentation](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/SCREENSHOT_MANAGER.md)
|
|
43
47
|
|
|
44
48
|
## Features
|
|
45
49
|
|
|
@@ -82,18 +86,33 @@ A TypeScript/JavaScript wrapper for the Steamworks SDK using Koffi FFI, designed
|
|
|
82
86
|
- ✅ Open overlay web browser to URLs
|
|
83
87
|
- ✅ Open store pages with purchase options
|
|
84
88
|
- ✅ Show invite dialogs for multiplayer sessions
|
|
85
|
-
- **Cloud Storage API**: Complete Steam Cloud (Remote Storage) integration (
|
|
89
|
+
- **Cloud Storage API**: Complete Steam Cloud (Remote Storage) integration (17 functions)
|
|
86
90
|
- ✅ File operations (write, read, delete, check existence)
|
|
87
91
|
- ✅ File metadata (size, timestamp, persistence status)
|
|
88
92
|
- ✅ File listing (count, iterate, get all with details)
|
|
89
93
|
- ✅ Quota management (track storage usage and limits)
|
|
90
94
|
- ✅ Cloud settings (check/toggle cloud sync for account and app)
|
|
91
|
-
-
|
|
95
|
+
- ✅ Batch writes (atomic multi-file operations)
|
|
96
|
+
- **Workshop API**: Complete Steam Workshop/UGC integration (30 functions)
|
|
92
97
|
- ✅ Subscription management (subscribe, unsubscribe, list items)
|
|
93
98
|
- ✅ Item state & information (download progress, installation info)
|
|
94
99
|
- ✅ Query operations (text search, browse, filter Workshop content)
|
|
95
100
|
- ✅ Item creation & update (create, upload, manage your Workshop items)
|
|
96
101
|
- ✅ Voting & favorites (vote on items, manage favorites)
|
|
102
|
+
- ✅ Item deletion (permanently delete your Workshop items)
|
|
103
|
+
- **Input API**: Complete Steam Input (controller) support (35+ functions) ⚠️ _Tested with virtual gamepad only_
|
|
104
|
+
- ✅ Controller detection (Xbox, PlayStation, Switch, Steam Controller, Steam Deck)
|
|
105
|
+
- ✅ Action sets and layers (menu controls, gameplay controls, etc.)
|
|
106
|
+
- ✅ Digital actions (buttons) and analog actions (sticks/triggers)
|
|
107
|
+
- ✅ Motion data (gyro, accelerometer for supported controllers)
|
|
108
|
+
- ✅ Haptics (vibration, LED control for DualShock/DualSense)
|
|
109
|
+
- **Screenshots API**: Steam Screenshots integration (9 functions)
|
|
110
|
+
- ✅ Screenshot capture (programmatic and user-triggered)
|
|
111
|
+
- ✅ Add existing images to Steam library
|
|
112
|
+
- ✅ Location and user tagging (geotag, tag friends)
|
|
113
|
+
- ✅ Workshop integration (tag published files)
|
|
114
|
+
- ✅ Screenshot hooks (intercept F12 screenshots)
|
|
115
|
+
- ✅ VR screenshot support
|
|
97
116
|
- **Steamworks Integration**: Direct FFI calls to Steamworks C++ SDK
|
|
98
117
|
- **Cross-Platform**: Windows, macOS, and Linux support
|
|
99
118
|
- **Easy Setup**: Simple installation with clear SDK setup guide
|
|
@@ -113,23 +132,26 @@ npm install steamworks-ffi-node
|
|
|
113
132
|
### Setup
|
|
114
133
|
|
|
115
134
|
1. **Download Steamworks SDK** (required separately due to licensing):
|
|
135
|
+
|
|
116
136
|
- Visit [Steamworks Partner site](https://partner.steamgames.com/)
|
|
117
137
|
- Download the latest Steamworks SDK
|
|
118
138
|
- Extract and copy `redistributable_bin` folder to your project
|
|
119
139
|
- See [STEAMWORKS_SDK_SETUP.md](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/STEAMWORKS_SDK_SETUP.md) for detailed instructions
|
|
120
140
|
|
|
121
141
|
2. **Create `steam_appid.txt` (optional)** in your project root:
|
|
142
|
+
|
|
122
143
|
```bash
|
|
123
144
|
echo "480" > steam_appid.txt # Use 480 for testing, or your Steam App ID
|
|
124
145
|
```
|
|
125
|
-
|
|
146
|
+
|
|
147
|
+
_Note: You can skip this file and pass the App ID directly to `steam.init(appId)` instead_
|
|
126
148
|
|
|
127
149
|
3. **Make sure Steam is running** and you're logged in
|
|
128
150
|
|
|
129
151
|
### Basic Usage
|
|
130
152
|
|
|
131
153
|
```typescript
|
|
132
|
-
import SteamworksSDK from
|
|
154
|
+
import SteamworksSDK from "steamworks-ffi-node";
|
|
133
155
|
|
|
134
156
|
// Helper to auto-start callback polling
|
|
135
157
|
function startCallbackPolling(steam: SteamworksSDK, interval: number = 1000) {
|
|
@@ -145,48 +167,50 @@ const initialized = steam.init({ appId: 480 }); // Your Steam App ID
|
|
|
145
167
|
if (initialized) {
|
|
146
168
|
// Start callback polling automatically (required for async operations)
|
|
147
169
|
const callbackInterval = startCallbackPolling(steam, 1000);
|
|
148
|
-
|
|
170
|
+
|
|
149
171
|
// Get current Steam language for localization
|
|
150
172
|
const language = steam.getCurrentGameLanguage();
|
|
151
|
-
console.log(
|
|
152
|
-
|
|
173
|
+
console.log("Steam language:", language); // e.g., 'english', 'french', 'german'
|
|
174
|
+
|
|
153
175
|
// Get achievements from Steam servers
|
|
154
176
|
const achievements = await steam.achievements.getAllAchievements();
|
|
155
|
-
console.log(
|
|
156
|
-
|
|
177
|
+
console.log("Steam achievements:", achievements);
|
|
178
|
+
|
|
157
179
|
// Unlock achievement (permanent in Steam!)
|
|
158
|
-
await steam.achievements.unlockAchievement(
|
|
159
|
-
|
|
180
|
+
await steam.achievements.unlockAchievement("ACH_WIN_ONE_GAME");
|
|
181
|
+
|
|
160
182
|
// Check unlock status from Steam
|
|
161
|
-
const isUnlocked = await steam.achievements.isAchievementUnlocked(
|
|
162
|
-
|
|
163
|
-
|
|
183
|
+
const isUnlocked = await steam.achievements.isAchievementUnlocked(
|
|
184
|
+
"ACH_WIN_ONE_GAME"
|
|
185
|
+
);
|
|
186
|
+
console.log("Achievement unlocked:", isUnlocked);
|
|
187
|
+
|
|
164
188
|
// Track user statistics
|
|
165
|
-
const kills = await steam.stats.getStatInt(
|
|
166
|
-
await steam.stats.setStatInt(
|
|
167
|
-
|
|
189
|
+
const kills = (await steam.stats.getStatInt("total_kills")) || 0;
|
|
190
|
+
await steam.stats.setStatInt("total_kills", kills + 1);
|
|
191
|
+
|
|
168
192
|
// Get global statistics
|
|
169
193
|
await steam.stats.requestGlobalStats(7);
|
|
170
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
194
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
171
195
|
steam.runCallbacks();
|
|
172
|
-
const globalKills = await steam.stats.getGlobalStatInt(
|
|
173
|
-
console.log(
|
|
174
|
-
|
|
196
|
+
const globalKills = await steam.stats.getGlobalStatInt("global.total_kills");
|
|
197
|
+
console.log("Total kills worldwide:", globalKills);
|
|
198
|
+
|
|
175
199
|
// Work with leaderboards
|
|
176
200
|
const leaderboard = await steam.leaderboards.findOrCreateLeaderboard(
|
|
177
|
-
|
|
201
|
+
"HighScores",
|
|
178
202
|
1, // Descending (higher is better)
|
|
179
|
-
0
|
|
203
|
+
0 // Numeric display
|
|
180
204
|
);
|
|
181
|
-
|
|
205
|
+
|
|
182
206
|
if (leaderboard) {
|
|
183
207
|
// Upload score
|
|
184
208
|
await steam.leaderboards.uploadLeaderboardScore(
|
|
185
209
|
leaderboard.handle,
|
|
186
210
|
1000,
|
|
187
|
-
1
|
|
211
|
+
1 // Keep best score
|
|
188
212
|
);
|
|
189
|
-
|
|
213
|
+
|
|
190
214
|
// Download top 10 scores
|
|
191
215
|
const topScores = await steam.leaderboards.downloadLeaderboardEntries(
|
|
192
216
|
leaderboard.handle,
|
|
@@ -194,36 +218,38 @@ if (initialized) {
|
|
|
194
218
|
0,
|
|
195
219
|
9
|
|
196
220
|
);
|
|
197
|
-
console.log(
|
|
221
|
+
console.log("Top 10 scores:", topScores);
|
|
198
222
|
}
|
|
199
|
-
|
|
223
|
+
|
|
200
224
|
// Access friends and social features
|
|
201
225
|
const personaName = steam.friends.getPersonaName();
|
|
202
226
|
const friendCount = steam.friends.getFriendCount(4); // All friends
|
|
203
227
|
console.log(`${personaName} has ${friendCount} friends`);
|
|
204
|
-
|
|
228
|
+
|
|
205
229
|
// Get all friends with details
|
|
206
230
|
const allFriends = steam.friends.getAllFriends(4); // All friends
|
|
207
|
-
allFriends.slice(0, 5).forEach(friend => {
|
|
231
|
+
allFriends.slice(0, 5).forEach((friend) => {
|
|
208
232
|
const name = steam.friends.getFriendPersonaName(friend.steamId);
|
|
209
233
|
const state = steam.friends.getFriendPersonaState(friend.steamId);
|
|
210
234
|
const level = steam.friends.getFriendSteamLevel(friend.steamId);
|
|
211
235
|
console.log(`${name}: Level ${level}, Status: ${state}`);
|
|
212
|
-
|
|
236
|
+
|
|
213
237
|
// Get avatar handles
|
|
214
238
|
const smallAvatar = steam.friends.getSmallFriendAvatar(friend.steamId);
|
|
215
239
|
const mediumAvatar = steam.friends.getMediumFriendAvatar(friend.steamId);
|
|
216
240
|
if (smallAvatar > 0) {
|
|
217
|
-
console.log(
|
|
241
|
+
console.log(
|
|
242
|
+
` Avatar handles: small=${smallAvatar}, medium=${mediumAvatar}`
|
|
243
|
+
);
|
|
218
244
|
}
|
|
219
|
-
|
|
245
|
+
|
|
220
246
|
// Check if playing a game
|
|
221
247
|
const gameInfo = steam.friends.getFriendGamePlayed(friend.steamId);
|
|
222
248
|
if (gameInfo) {
|
|
223
249
|
console.log(` Playing: App ${gameInfo.gameId}`);
|
|
224
250
|
}
|
|
225
251
|
});
|
|
226
|
-
|
|
252
|
+
|
|
227
253
|
// Check friend groups (tags)
|
|
228
254
|
const groupCount = steam.friends.getFriendsGroupCount();
|
|
229
255
|
if (groupCount > 0) {
|
|
@@ -232,7 +258,7 @@ if (initialized) {
|
|
|
232
258
|
const members = steam.friends.getFriendsGroupMembersList(groupId);
|
|
233
259
|
console.log(`Group "${groupName}" has ${members.length} members`);
|
|
234
260
|
}
|
|
235
|
-
|
|
261
|
+
|
|
236
262
|
// Check recently played with
|
|
237
263
|
const coplayCount = steam.friends.getCoplayFriendCount();
|
|
238
264
|
if (coplayCount > 0) {
|
|
@@ -241,107 +267,123 @@ if (initialized) {
|
|
|
241
267
|
const coplayTime = steam.friends.getFriendCoplayTime(recentPlayer);
|
|
242
268
|
console.log(`Recently played with ${playerName}`);
|
|
243
269
|
}
|
|
244
|
-
|
|
270
|
+
|
|
245
271
|
// Set rich presence for custom status
|
|
246
|
-
steam.richPresence.setRichPresence(
|
|
247
|
-
steam.richPresence.setRichPresence(
|
|
248
|
-
|
|
272
|
+
steam.richPresence.setRichPresence("status", "In Main Menu");
|
|
273
|
+
steam.richPresence.setRichPresence("connect", "+connect server:27015");
|
|
274
|
+
|
|
249
275
|
// Open Steam overlay
|
|
250
|
-
steam.overlay.activateGameOverlay(
|
|
251
|
-
steam.overlay.activateGameOverlayToWebPage(
|
|
252
|
-
|
|
276
|
+
steam.overlay.activateGameOverlay("Friends"); // Open friends list
|
|
277
|
+
steam.overlay.activateGameOverlayToWebPage("https://example.com/wiki"); // Open wiki
|
|
278
|
+
|
|
253
279
|
// Steam Cloud storage operations
|
|
254
|
-
const saveData = { level: 5, score: 1000, inventory: [
|
|
280
|
+
const saveData = { level: 5, score: 1000, inventory: ["sword", "shield"] };
|
|
255
281
|
const buffer = Buffer.from(JSON.stringify(saveData));
|
|
256
|
-
|
|
282
|
+
|
|
257
283
|
// Write save file to Steam Cloud
|
|
258
|
-
const written = steam.cloud.fileWrite(
|
|
284
|
+
const written = steam.cloud.fileWrite("savegame.json", buffer);
|
|
259
285
|
if (written) {
|
|
260
|
-
console.log(
|
|
286
|
+
console.log("✅ Save uploaded to Steam Cloud");
|
|
261
287
|
}
|
|
262
|
-
|
|
288
|
+
|
|
263
289
|
// Check cloud quota
|
|
264
290
|
const quota = steam.cloud.getQuota();
|
|
265
|
-
console.log(
|
|
266
|
-
|
|
291
|
+
console.log(
|
|
292
|
+
`Cloud storage: ${quota.usedBytes}/${
|
|
293
|
+
quota.totalBytes
|
|
294
|
+
} bytes (${quota.percentUsed.toFixed(2)}%)`
|
|
295
|
+
);
|
|
296
|
+
|
|
267
297
|
// Read save file from Steam Cloud
|
|
268
|
-
if (steam.cloud.fileExists(
|
|
269
|
-
const result = steam.cloud.fileRead(
|
|
298
|
+
if (steam.cloud.fileExists("savegame.json")) {
|
|
299
|
+
const result = steam.cloud.fileRead("savegame.json");
|
|
270
300
|
if (result.success && result.data) {
|
|
271
301
|
const loadedSave = JSON.parse(result.data.toString());
|
|
272
|
-
console.log(
|
|
302
|
+
console.log(
|
|
303
|
+
`Loaded save: Level ${loadedSave.level}, Score ${loadedSave.score}`
|
|
304
|
+
);
|
|
273
305
|
}
|
|
274
306
|
}
|
|
275
|
-
|
|
307
|
+
|
|
276
308
|
// List all cloud files
|
|
277
309
|
const cloudFiles = steam.cloud.getAllFiles();
|
|
278
310
|
console.log(`Steam Cloud contains ${cloudFiles.length} files:`);
|
|
279
|
-
cloudFiles.forEach(file => {
|
|
311
|
+
cloudFiles.forEach((file) => {
|
|
280
312
|
const kb = (file.size / 1024).toFixed(2);
|
|
281
|
-
const status = file.persisted ?
|
|
313
|
+
const status = file.persisted ? "☁️" : "⏳";
|
|
282
314
|
console.log(`${status} ${file.name} - ${kb} KB`);
|
|
283
315
|
});
|
|
284
|
-
|
|
316
|
+
|
|
285
317
|
// Steam Workshop operations
|
|
286
318
|
// Subscribe to a Workshop item
|
|
287
319
|
const subscribeResult = await steam.workshop.subscribeItem(123456789n);
|
|
288
320
|
if (subscribeResult.success) {
|
|
289
|
-
console.log(
|
|
321
|
+
console.log("✅ Subscribed to Workshop item");
|
|
290
322
|
}
|
|
291
|
-
|
|
323
|
+
|
|
292
324
|
// Get all subscribed items
|
|
293
325
|
const subscribedItems = steam.workshop.getSubscribedItems();
|
|
294
326
|
console.log(`Subscribed to ${subscribedItems.length} Workshop items`);
|
|
295
|
-
|
|
327
|
+
|
|
296
328
|
// Query Workshop items with text search
|
|
297
329
|
const query = steam.workshop.createQueryAllUGCRequest(
|
|
298
|
-
11,
|
|
299
|
-
0,
|
|
300
|
-
480,
|
|
301
|
-
480,
|
|
302
|
-
1
|
|
330
|
+
11, // RankedByTextSearch - for text search queries
|
|
331
|
+
0, // Items
|
|
332
|
+
480, // Creator App ID
|
|
333
|
+
480, // Consumer App ID
|
|
334
|
+
1 // Page 1
|
|
303
335
|
);
|
|
304
|
-
|
|
336
|
+
|
|
305
337
|
if (query) {
|
|
306
338
|
// Set search text to filter results
|
|
307
|
-
steam.workshop.setSearchText(query,
|
|
308
|
-
|
|
339
|
+
steam.workshop.setSearchText(query, "map");
|
|
340
|
+
|
|
309
341
|
const queryResult = await steam.workshop.sendQueryUGCRequest(query);
|
|
310
342
|
if (queryResult) {
|
|
311
|
-
console.log(
|
|
312
|
-
|
|
343
|
+
console.log(
|
|
344
|
+
`Found ${queryResult.numResults} Workshop items matching "map"`
|
|
345
|
+
);
|
|
346
|
+
|
|
313
347
|
// Get details for each item
|
|
314
348
|
for (let i = 0; i < queryResult.numResults; i++) {
|
|
315
349
|
const details = steam.workshop.getQueryUGCResult(query, i);
|
|
316
350
|
if (details) {
|
|
317
351
|
console.log(`📦 ${details.title} by ${details.steamIDOwner}`);
|
|
318
|
-
console.log(
|
|
352
|
+
console.log(
|
|
353
|
+
` Score: ${details.score}, Downloads: ${details.numUniqueSubscriptions}`
|
|
354
|
+
);
|
|
319
355
|
}
|
|
320
356
|
}
|
|
321
357
|
}
|
|
322
358
|
steam.workshop.releaseQueryUGCRequest(query);
|
|
323
359
|
}
|
|
324
|
-
|
|
360
|
+
|
|
325
361
|
// Check download progress for subscribed items
|
|
326
|
-
subscribedItems.forEach(itemId => {
|
|
362
|
+
subscribedItems.forEach((itemId) => {
|
|
327
363
|
const state = steam.workshop.getItemState(itemId);
|
|
328
364
|
const stateFlags = [];
|
|
329
|
-
if (state & 1) stateFlags.push(
|
|
330
|
-
if (state & 4) stateFlags.push(
|
|
331
|
-
if (state & 8) stateFlags.push(
|
|
332
|
-
if (state & 16) stateFlags.push(
|
|
333
|
-
|
|
334
|
-
console.log(`Item ${itemId}: ${stateFlags.join(
|
|
335
|
-
|
|
336
|
-
if (state & 16) {
|
|
365
|
+
if (state & 1) stateFlags.push("Subscribed");
|
|
366
|
+
if (state & 4) stateFlags.push("Needs Update");
|
|
367
|
+
if (state & 8) stateFlags.push("Installed");
|
|
368
|
+
if (state & 16) stateFlags.push("Downloading");
|
|
369
|
+
|
|
370
|
+
console.log(`Item ${itemId}: ${stateFlags.join(", ")}`);
|
|
371
|
+
|
|
372
|
+
if (state & 16) {
|
|
373
|
+
// If downloading
|
|
337
374
|
const progress = steam.workshop.getItemDownloadInfo(itemId);
|
|
338
375
|
if (progress) {
|
|
339
|
-
const percent = (progress.downloaded / progress.total * 100).toFixed(
|
|
340
|
-
|
|
376
|
+
const percent = ((progress.downloaded / progress.total) * 100).toFixed(
|
|
377
|
+
1
|
|
378
|
+
);
|
|
379
|
+
console.log(
|
|
380
|
+
` Download: ${percent}% (${progress.downloaded}/${progress.total} bytes)`
|
|
381
|
+
);
|
|
341
382
|
}
|
|
342
383
|
}
|
|
343
|
-
|
|
344
|
-
if (state & 8) {
|
|
384
|
+
|
|
385
|
+
if (state & 8) {
|
|
386
|
+
// If installed
|
|
345
387
|
const info = steam.workshop.getItemInstallInfo(itemId);
|
|
346
388
|
if (info.success) {
|
|
347
389
|
console.log(` Installed at: ${info.folder}`);
|
|
@@ -359,13 +401,13 @@ steam.shutdown();
|
|
|
359
401
|
|
|
360
402
|
```javascript
|
|
361
403
|
// Option 1: ESM Named import
|
|
362
|
-
import { SteamworksSDK } from
|
|
404
|
+
import { SteamworksSDK } from "steamworks-ffi-node";
|
|
363
405
|
|
|
364
406
|
// Option 2: CommonJs named import (recommended - no .default needed)
|
|
365
|
-
const { SteamworksSDK } = require(
|
|
407
|
+
const { SteamworksSDK } = require("steamworks-ffi-node");
|
|
366
408
|
|
|
367
409
|
// Option 3: CommonJs default named import (also works)
|
|
368
|
-
const SteamworksSDK = require(
|
|
410
|
+
const SteamworksSDK = require("steamworks-ffi-node").default;
|
|
369
411
|
|
|
370
412
|
// Helper to auto-start callback polling
|
|
371
413
|
function startCallbackPolling(steam, interval = 1000) {
|
|
@@ -376,24 +418,24 @@ function startCallbackPolling(steam, interval = 1000) {
|
|
|
376
418
|
|
|
377
419
|
async function example() {
|
|
378
420
|
const steam = SteamworksSDK.getInstance();
|
|
379
|
-
|
|
421
|
+
|
|
380
422
|
if (steam.init({ appId: 480 })) {
|
|
381
423
|
// Start callback polling automatically
|
|
382
424
|
const callbackInterval = startCallbackPolling(steam, 1000);
|
|
383
|
-
|
|
425
|
+
|
|
384
426
|
const achievements = await steam.achievements.getAllAchievements();
|
|
385
427
|
console.log(`Found ${achievements.length} achievements`);
|
|
386
|
-
|
|
428
|
+
|
|
387
429
|
// Unlock first locked achievement
|
|
388
|
-
const locked = achievements.find(a => !a.unlocked);
|
|
430
|
+
const locked = achievements.find((a) => !a.unlocked);
|
|
389
431
|
if (locked) {
|
|
390
432
|
await steam.achievements.unlockAchievement(locked.apiName);
|
|
391
433
|
}
|
|
392
|
-
|
|
434
|
+
|
|
393
435
|
// Cleanup
|
|
394
436
|
clearInterval(callbackInterval);
|
|
395
437
|
}
|
|
396
|
-
|
|
438
|
+
|
|
397
439
|
steam.shutdown();
|
|
398
440
|
}
|
|
399
441
|
|
|
@@ -403,6 +445,7 @@ example();
|
|
|
403
445
|
### Testing with Spacewar
|
|
404
446
|
|
|
405
447
|
For immediate testing, use Spacewar (App ID 480):
|
|
448
|
+
|
|
406
449
|
- Free Steam app for testing Steamworks features
|
|
407
450
|
- Add to Steam library: `steam://install/480` or search "Spacewar" in Steam
|
|
408
451
|
- Launch it once, then you can test with App ID 480
|
|
@@ -414,14 +457,16 @@ Complete documentation for all APIs is available in the [docs folder](https://gi
|
|
|
414
457
|
➡️ **[View Complete Documentation](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/README.md)**
|
|
415
458
|
|
|
416
459
|
### API Guides:
|
|
460
|
+
|
|
417
461
|
- **[Achievement Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/ACHIEVEMENT_MANAGER.md)** - Complete achievement system (20 functions)
|
|
418
462
|
- **[Stats Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/STATS_MANAGER.md)** - User and global statistics (14 functions)
|
|
419
463
|
- **[Leaderboard Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/LEADERBOARD_MANAGER.md)** - Leaderboard operations (7 functions)
|
|
420
464
|
- **[Friends Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/FRIENDS_MANAGER.md)** - Friends and social features (22 functions)
|
|
421
465
|
- **[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
466
|
- **[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/
|
|
467
|
+
- **[Cloud Storage Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/CLOUD_MANAGER.md)** - Steam Cloud file operations (14 functions)
|
|
424
468
|
- **[Workshop Manager](https://github.com/ArtyProf/steamworks-ffi-node/blob/main/docs/WORKSHOP_MANAGER.md)** - Steam Workshop/UGC operations (29 functions)
|
|
469
|
+
- **[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
470
|
|
|
426
471
|
## Steamworks Integration
|
|
427
472
|
|
|
@@ -437,11 +482,12 @@ const achievements = await steam.achievements.getAllAchievements();
|
|
|
437
482
|
console.log(achievements); // Achievement data from your Steam app
|
|
438
483
|
|
|
439
484
|
// Permanent achievement unlock in Steam
|
|
440
|
-
await steam.achievements.unlockAchievement(
|
|
485
|
+
await steam.achievements.unlockAchievement("YOUR_ACHIEVEMENT");
|
|
441
486
|
// ^ This shows up in Steam overlay and is saved permanently
|
|
442
487
|
```
|
|
443
488
|
|
|
444
489
|
**What happens when you unlock an achievement:**
|
|
490
|
+
|
|
445
491
|
- ✅ Steam overlay notification appears
|
|
446
492
|
- ✅ Achievement saved to Steam servers permanently
|
|
447
493
|
- ✅ Syncs across all devices
|
|
@@ -454,21 +500,21 @@ For Electron applications, use it in your main process:
|
|
|
454
500
|
|
|
455
501
|
```typescript
|
|
456
502
|
// main.ts
|
|
457
|
-
import { app } from
|
|
458
|
-
import SteamworksSDK from
|
|
503
|
+
import { app } from "electron";
|
|
504
|
+
import SteamworksSDK from "steamworks-ffi-node";
|
|
459
505
|
|
|
460
506
|
app.whenReady().then(() => {
|
|
461
507
|
const steam = SteamworksSDK.getInstance();
|
|
462
|
-
|
|
508
|
+
|
|
463
509
|
if (steam.init({ appId: YOUR_STEAM_APP_ID })) {
|
|
464
|
-
console.log(
|
|
465
|
-
|
|
510
|
+
console.log("Steam initialized in Electron!");
|
|
511
|
+
|
|
466
512
|
// Handle achievement unlocks from renderer process
|
|
467
513
|
// via IPC if needed
|
|
468
514
|
}
|
|
469
515
|
});
|
|
470
516
|
|
|
471
|
-
app.on(
|
|
517
|
+
app.on("before-quit", () => {
|
|
472
518
|
const steam = SteamworksSDK.getInstance();
|
|
473
519
|
steam.shutdown();
|
|
474
520
|
});
|
|
@@ -482,42 +528,46 @@ The library searches for the SDK in standard locations within your Electron app
|
|
|
482
528
|
|
|
483
529
|
## Requirements
|
|
484
530
|
|
|
485
|
-
- **Node.js**: 18+
|
|
531
|
+
- **Node.js**: 18+
|
|
486
532
|
- **Steam Client**: Must be running and logged in
|
|
487
533
|
- **Steam App ID**: Get yours at [Steamworks Partner](https://partner.steamgames.com/)
|
|
488
534
|
- **steam_appid.txt**: Optional - create in your project root OR pass to `steam.init(appId)`
|
|
489
535
|
|
|
490
536
|
### Platform Support
|
|
537
|
+
|
|
491
538
|
- ✅ **Windows**: steam_api64.dll / steam_api.dll
|
|
492
|
-
- ✅ **macOS**: libsteam_api.dylib
|
|
539
|
+
- ✅ **macOS**: libsteam_api.dylib
|
|
493
540
|
- ✅ **Linux**: libsteam_api.so
|
|
494
541
|
|
|
495
542
|
**Steamworks SDK Version**: v1.62 (Latest)
|
|
496
543
|
|
|
497
|
-
|
|
544
|
+
_Note: You must download and install the SDK redistributables separately as described in the Setup section above._
|
|
498
545
|
|
|
499
546
|
## Troubleshooting
|
|
500
547
|
|
|
501
548
|
### "SteamAPI_Init failed"
|
|
549
|
+
|
|
502
550
|
- ❌ Steam client not running → **Solution**: Start Steam and log in
|
|
503
551
|
- ❌ No App ID specified → **Solution**: Create `steam_appid.txt` in project root OR pass App ID to `steam.init(appId)`
|
|
504
552
|
- ❌ Invalid App ID → **Solution**: Use 480 for testing, or your registered App ID
|
|
505
553
|
|
|
506
554
|
### "Cannot find module 'steamworks-ffi-node'"
|
|
555
|
+
|
|
507
556
|
- ❌ Package not installed → **Solution**: Run `npm install steamworks-ffi-node`
|
|
508
557
|
|
|
509
558
|
### Achievement operations not working
|
|
559
|
+
|
|
510
560
|
- ❌ Not initialized → **Solution**: Call `steam.init({ appId })` first
|
|
511
561
|
- ❌ No achievements configured → **Solution**: Configure achievements in Steamworks Partner site
|
|
512
562
|
- ❌ Using Spacewar → **Note**: Spacewar may not have achievements, use your own App ID
|
|
513
563
|
|
|
514
564
|
### Electron-specific issues
|
|
565
|
+
|
|
515
566
|
- ❌ Initialized in renderer → **Solution**: Only initialize in main process
|
|
516
567
|
- ❌ Not cleaning up → **Solution**: Call `shutdown()` in `before-quit` event
|
|
517
568
|
- ❌ "Steamworks SDK library not found" in packaged app → **Solution**: Include SDK redistributables in your build (see Electron Packaging section above)
|
|
518
569
|
- ❌ Native module errors in packaged app → **Solution**: Ensure Steamworks SDK files are properly included in your app bundle
|
|
519
570
|
|
|
520
|
-
|
|
521
571
|
## How to Support This Project
|
|
522
572
|
|
|
523
573
|
You can support the development of this library by wishlisting, subscribing, and purchasing the app **AFK Companion** on Steam:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SteamLibraryLoader } from './SteamLibraryLoader';
|
|
2
2
|
import { SteamAPICore } from './SteamAPICore';
|
|
3
|
-
import { CloudFileInfo, CloudQuota, CloudFileReadResult } from '../types';
|
|
3
|
+
import { CloudFileInfo, CloudQuota, CloudFileReadResult, CloudBatchWriteResult } from '../types';
|
|
4
4
|
/**
|
|
5
5
|
* Manager for Steam Cloud / Remote Storage operations
|
|
6
6
|
*
|
|
@@ -315,5 +315,90 @@ export declare class SteamCloudManager {
|
|
|
315
315
|
* ```
|
|
316
316
|
*/
|
|
317
317
|
setCloudEnabledForApp(enabled: boolean): void;
|
|
318
|
+
/**
|
|
319
|
+
* Begins a batch of file write operations
|
|
320
|
+
*
|
|
321
|
+
* @returns True if the batch was started successfully, false otherwise
|
|
322
|
+
*
|
|
323
|
+
* @remarks
|
|
324
|
+
* Use this when you need to update multiple files atomically, such as a game save
|
|
325
|
+
* that consists of multiple files. Steam will treat all writes between
|
|
326
|
+
* BeginFileWriteBatch() and EndFileWriteBatch() as a single atomic operation.
|
|
327
|
+
*
|
|
328
|
+
* If a batch is started but not ended (e.g., due to a crash), Steam will
|
|
329
|
+
* rollback the changes when the user next plays the game.
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```typescript
|
|
333
|
+
* // Write multiple save files atomically
|
|
334
|
+
* const success = steam.cloud.beginFileWriteBatch();
|
|
335
|
+
* if (success) {
|
|
336
|
+
* steam.cloud.fileWrite('save_meta.json', metaBuffer);
|
|
337
|
+
* steam.cloud.fileWrite('save_world.bin', worldBuffer);
|
|
338
|
+
* steam.cloud.fileWrite('save_inventory.json', inventoryBuffer);
|
|
339
|
+
* steam.cloud.endFileWriteBatch();
|
|
340
|
+
* }
|
|
341
|
+
* ```
|
|
342
|
+
*
|
|
343
|
+
* @see {@link endFileWriteBatch}
|
|
344
|
+
*/
|
|
345
|
+
beginFileWriteBatch(): boolean;
|
|
346
|
+
/**
|
|
347
|
+
* Ends a batch of file write operations
|
|
348
|
+
*
|
|
349
|
+
* @returns True if the batch was ended successfully, false otherwise
|
|
350
|
+
*
|
|
351
|
+
* @remarks
|
|
352
|
+
* Call this after all files in the batch have been written. If this returns
|
|
353
|
+
* true, all the files written since BeginFileWriteBatch() will be committed
|
|
354
|
+
* atomically.
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```typescript
|
|
358
|
+
* // Complete a batch write
|
|
359
|
+
* steam.cloud.beginFileWriteBatch();
|
|
360
|
+
* steam.cloud.fileWrite('file1.dat', data1);
|
|
361
|
+
* steam.cloud.fileWrite('file2.dat', data2);
|
|
362
|
+
* const committed = steam.cloud.endFileWriteBatch();
|
|
363
|
+
* if (committed) {
|
|
364
|
+
* console.log('All files saved atomically');
|
|
365
|
+
* }
|
|
366
|
+
* ```
|
|
367
|
+
*
|
|
368
|
+
* @see {@link beginFileWriteBatch}
|
|
369
|
+
*/
|
|
370
|
+
endFileWriteBatch(): boolean;
|
|
371
|
+
/**
|
|
372
|
+
* Writes multiple files atomically as a batch operation
|
|
373
|
+
*
|
|
374
|
+
* @param files - Array of objects containing filename and data pairs
|
|
375
|
+
* @returns Object with success status and individual file results
|
|
376
|
+
*
|
|
377
|
+
* @remarks
|
|
378
|
+
* This is a convenience method that wraps BeginFileWriteBatch(), multiple
|
|
379
|
+
* fileWrite() calls, and EndFileWriteBatch() into a single operation.
|
|
380
|
+
*
|
|
381
|
+
* If any file fails to write, the entire batch is considered failed and
|
|
382
|
+
* Steam may rollback the changes.
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* ```typescript
|
|
386
|
+
* const result = steam.cloud.writeFilesBatch([
|
|
387
|
+
* { filename: 'save_meta.json', data: Buffer.from(JSON.stringify(meta)) },
|
|
388
|
+
* { filename: 'save_world.bin', data: worldBuffer },
|
|
389
|
+
* { filename: 'save_inventory.json', data: Buffer.from(JSON.stringify(inventory)) }
|
|
390
|
+
* ]);
|
|
391
|
+
*
|
|
392
|
+
* if (result.success) {
|
|
393
|
+
* console.log(`All ${result.filesWritten} files saved`);
|
|
394
|
+
* } else {
|
|
395
|
+
* console.error('Batch write failed:', result.failedFiles);
|
|
396
|
+
* }
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
399
|
+
writeFilesBatch(files: Array<{
|
|
400
|
+
filename: string;
|
|
401
|
+
data: Buffer;
|
|
402
|
+
}>): CloudBatchWriteResult;
|
|
318
403
|
}
|
|
319
404
|
//# sourceMappingURL=SteamCloudManager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SteamCloudManager.d.ts","sourceRoot":"","sources":["../../src/internal/SteamCloudManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EACL,aAAa,EACb,UAAU,
|
|
1
|
+
{"version":3,"file":"SteamCloudManager.d.ts","sourceRoot":"","sources":["../../src/internal/SteamCloudManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EACL,aAAa,EACb,UAAU,EACV,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,qBAAa,iBAAiB;IAC5B,kDAAkD;IAClD,OAAO,CAAC,aAAa,CAAqB;IAE1C,gEAAgE;IAChE,OAAO,CAAC,OAAO,CAAe;IAE9B;;;;;OAKG;gBACS,aAAa,EAAE,kBAAkB,EAAE,OAAO,EAAE,YAAY;IAKpE;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IA2BlD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB;IAiD/C;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAkBrC;;;;;;;;;;;;;;;;;;OAkBG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAkBrC;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAkBrC;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAkB1C;;;;;;;;;;OAUG;IACH,YAAY,IAAI,MAAM;IAkBtB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IA0CxE;;;;;;;;;;;;;;;;OAgBG;IACH,WAAW,IAAI,aAAa,EAAE;IAuB9B;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAkBxC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,IAAI,UAAU;IA2CtB;;;;;;;;;;;;;;;OAeG;IACH,wBAAwB,IAAI,OAAO;IAkBnC;;;;;;;;;;;;;;;OAeG;IACH,oBAAoB,IAAI,OAAO;IAkB/B;;;;;;;;;;;;;;;OAeG;IACH,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAqB7C;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,mBAAmB,IAAI,OAAO;IAoB9B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,iBAAiB,IAAI,OAAO;IAoB5B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,qBAAqB;CA2BzF"}
|