steamworks-ffi-node 0.6.0 → 0.6.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.
Files changed (54) hide show
  1. package/README.md +74 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +5 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/SteamAPICore.d.ts +23 -0
  7. package/dist/internal/SteamAPICore.d.ts.map +1 -1
  8. package/dist/internal/SteamAPICore.js +30 -0
  9. package/dist/internal/SteamAPICore.js.map +1 -1
  10. package/dist/internal/SteamCallbackPoller.d.ts +72 -0
  11. package/dist/internal/SteamCallbackPoller.d.ts.map +1 -1
  12. package/dist/internal/SteamCallbackPoller.js +144 -2
  13. package/dist/internal/SteamCallbackPoller.js.map +1 -1
  14. package/dist/internal/SteamCloudManager.d.ts.map +1 -1
  15. package/dist/internal/SteamCloudManager.js +18 -18
  16. package/dist/internal/SteamCloudManager.js.map +1 -1
  17. package/dist/internal/SteamLibraryLoader.d.ts +27 -0
  18. package/dist/internal/SteamLibraryLoader.d.ts.map +1 -1
  19. package/dist/internal/SteamLibraryLoader.js +36 -0
  20. package/dist/internal/SteamLibraryLoader.js.map +1 -1
  21. package/dist/internal/SteamOverlayManager.js +21 -21
  22. package/dist/internal/SteamOverlayManager.js.map +1 -1
  23. package/dist/internal/SteamRichPresenceManager.js +18 -18
  24. package/dist/internal/SteamRichPresenceManager.js.map +1 -1
  25. package/dist/internal/SteamWorkshopManager.d.ts +602 -0
  26. package/dist/internal/SteamWorkshopManager.d.ts.map +1 -0
  27. package/dist/internal/SteamWorkshopManager.js +1426 -0
  28. package/dist/internal/SteamWorkshopManager.js.map +1 -0
  29. package/dist/internal/callbackTypes/SteamCallbackIds.d.ts +26 -0
  30. package/dist/internal/callbackTypes/SteamCallbackIds.d.ts.map +1 -0
  31. package/dist/internal/callbackTypes/SteamCallbackIds.js +35 -0
  32. package/dist/internal/callbackTypes/SteamCallbackIds.js.map +1 -0
  33. package/dist/internal/callbackTypes/SteamCallbackTypes.d.ts +89 -0
  34. package/dist/internal/callbackTypes/SteamCallbackTypes.d.ts.map +1 -0
  35. package/dist/internal/callbackTypes/SteamCallbackTypes.js +9 -0
  36. package/dist/internal/callbackTypes/SteamCallbackTypes.js.map +1 -0
  37. package/dist/internal/callbackTypes/index.d.ts +6 -0
  38. package/dist/internal/callbackTypes/index.d.ts.map +1 -0
  39. package/dist/internal/callbackTypes/index.js +22 -0
  40. package/dist/internal/callbackTypes/index.js.map +1 -0
  41. package/dist/steam.d.ts +42 -0
  42. package/dist/steam.d.ts.map +1 -1
  43. package/dist/steam.js +4 -0
  44. package/dist/steam.js.map +1 -1
  45. package/dist/types/index.d.ts +2 -0
  46. package/dist/types/index.d.ts.map +1 -1
  47. package/dist/types/index.js +3 -0
  48. package/dist/types/index.js.map +1 -1
  49. package/dist/types/workshop.d.ts +302 -0
  50. package/dist/types/workshop.d.ts.map +1 -0
  51. package/dist/types/workshop.js +203 -0
  52. package/dist/types/workshop.js.map +1 -0
  53. package/docs/README.md +12 -0
  54. package/package.json +3 -1
@@ -0,0 +1,1426 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SteamWorkshopManager = void 0;
37
+ const SteamCallbackPoller_1 = require("./SteamCallbackPoller");
38
+ const types_1 = require("../types");
39
+ const callbackTypes_1 = require("./callbackTypes");
40
+ const koffi = __importStar(require("koffi"));
41
+ // Manual buffer parsing for CreateItemResult_t callback
42
+ // Steam uses #pragma pack causing tight packing - Koffi can't handle this properly
43
+ // Layout: [int32:0-3][uint64:4-11][uint8:12] = 13 bytes, padded to 16
44
+ const CreateItemResult_t = koffi.struct('CreateItemResult_t', {
45
+ rawBytes: koffi.array('uint8', 16)
46
+ });
47
+ // Manual buffer parsing for SubmitItemUpdateResult_t callback
48
+ // Steam uses #pragma pack causing tight packing - Koffi can't handle this properly
49
+ // Layout: [int32:0-3][bool:4][padding:5-7][uint64:8-15] = 16 bytes
50
+ const SubmitItemUpdateResult_t = koffi.struct('SubmitItemUpdateResult_t', {
51
+ rawBytes: koffi.array('uint8', 16)
52
+ });
53
+ // Manual buffer parsing for RemoteStorageSubscribePublishedFileResult_t callback
54
+ // Steam uses #pragma pack causing tight packing - Koffi can't handle this properly
55
+ // Layout: [int32:0-3][uint64:4-11] = 12 bytes (NO padding!)
56
+ const RemoteStorageSubscribePublishedFileResult_t = koffi.struct('RemoteStorageSubscribePublishedFileResult_t', {
57
+ rawBytes: koffi.array('uint8', 12)
58
+ });
59
+ // Manual buffer parsing for RemoteStorageUnsubscribePublishedFileResult_t callback
60
+ // Steam uses #pragma pack causing tight packing - Koffi can't handle this properly
61
+ // Layout: [int32:0-3][uint64:4-11] = 12 bytes (NO padding!)
62
+ const RemoteStorageUnsubscribePublishedFileResult_t = koffi.struct('RemoteStorageUnsubscribePublishedFileResult_t', {
63
+ rawBytes: koffi.array('uint8', 12)
64
+ });
65
+ // Koffi struct for SteamUGCQueryCompleted_t callback
66
+ const SteamUGCQueryCompleted_t = koffi.struct('SteamUGCQueryCompleted_t', {
67
+ m_handle: 'uint64', // UGCQueryHandle_t
68
+ m_eResult: 'int', // EResult
69
+ m_unNumResultsReturned: 'uint32',
70
+ m_unTotalMatchingResults: 'uint32',
71
+ m_bCachedData: 'bool'
72
+ });
73
+ // Manual buffer parsing for SetUserItemVoteResult_t callback
74
+ // Steam uses #pragma pack causing tight packing - Koffi can't handle this properly
75
+ // Layout: [uint64:0-7][int32:8-11][bool:12] = 13 bytes
76
+ const SetUserItemVoteResult_t = koffi.struct('SetUserItemVoteResult_t', {
77
+ rawBytes: koffi.array('uint8', 13)
78
+ });
79
+ // Manual buffer parsing for GetUserItemVoteResult_t callback
80
+ // Steam uses #pragma pack causing tight packing - Koffi can't handle this properly
81
+ // Layout: [uint64:0-7][int32:8-11][bool×3:12-14] = 15 bytes
82
+ const GetUserItemVoteResult_t = koffi.struct('GetUserItemVoteResult_t', {
83
+ rawBytes: koffi.array('uint8', 15)
84
+ });
85
+ // Koffi struct for UserFavoriteItemsListChanged_t callback
86
+ const UserFavoriteItemsListChanged_t = koffi.struct('UserFavoriteItemsListChanged_t', {
87
+ m_nPublishedFileId: 'uint64', // PublishedFileId_t (8 bytes)
88
+ m_eResult: 'int', // EResult (4 bytes)
89
+ m_bWasAddRequest: 'bool' // bool (1 byte)
90
+ });
91
+ /**
92
+ * Manager for Steam Workshop / User Generated Content (UGC) operations
93
+ *
94
+ * The SteamWorkshopManager provides comprehensive access to Steam's Workshop system,
95
+ * allowing you to browse, subscribe to, create, and manage user-generated content.
96
+ *
97
+ * Key Features:
98
+ * - Query and browse Workshop items
99
+ * - Subscribe/unsubscribe to Workshop content
100
+ * - Download and install Workshop items
101
+ * - Create and update your own Workshop items
102
+ * - Vote on and favorite Workshop content
103
+ * - Track download and installation progress
104
+ *
105
+ * @remarks
106
+ * All methods require the Steam API to be initialized.
107
+ * Workshop items are automatically downloaded and installed when subscribed.
108
+ *
109
+ * @example Basic subscription
110
+ * ```typescript
111
+ * const steam = SteamworksSDK.getInstance();
112
+ * steam.init({ appId: 480 });
113
+ *
114
+ * // Subscribe to a Workshop item
115
+ * const itemId = BigInt('123456789');
116
+ * await steam.workshop.subscribeItem(itemId);
117
+ *
118
+ * // Check if subscribed
119
+ * const subscribed = steam.workshop.getSubscribedItems();
120
+ * console.log(`Subscribed to ${subscribed.length} items`);
121
+ *
122
+ * // Get installation info
123
+ * const info = steam.workshop.getItemInstallInfo(itemId);
124
+ * if (info) {
125
+ * console.log(`Installed at: ${info.folder}`);
126
+ * }
127
+ * ```
128
+ *
129
+ * @example Query Workshop items
130
+ * ```typescript
131
+ * // Query popular items
132
+ * const query = steam.workshop.createQueryAllUGCRequest(
133
+ * EUGCQuery.RankedByVote,
134
+ * EUGCMatchingUGCType.Items,
135
+ * 1 // page 1
136
+ * );
137
+ *
138
+ * // Results come through callbacks
139
+ * // Listen for query completion in your callback handler
140
+ * ```
141
+ *
142
+ * @see {@link https://partner.steamgames.com/doc/api/ISteamUGC ISteamUGC Documentation}
143
+ */
144
+ class SteamWorkshopManager {
145
+ constructor(libraryLoader, apiCore) {
146
+ this.libraryLoader = libraryLoader;
147
+ this.apiCore = apiCore;
148
+ this.callbackPoller = new SteamCallbackPoller_1.SteamCallbackPoller(libraryLoader, apiCore);
149
+ }
150
+ // ========================================
151
+ // Subscription Management
152
+ // ========================================
153
+ /**
154
+ * Subscribes to a Workshop item
155
+ *
156
+ * @param publishedFileId - The Workshop item ID to subscribe to
157
+ * @returns True if subscription was successful, false otherwise
158
+ *
159
+ * @remarks
160
+ * - Item will be automatically downloaded and installed
161
+ * - Use getItemInstallInfo() to check installation status
162
+ * - Use getItemDownloadInfo() to track download progress
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * const itemId = BigInt('123456789');
167
+ * const success = await steam.workshop.subscribeItem(itemId);
168
+ *
169
+ * if (success) {
170
+ * console.log('Successfully subscribed!');
171
+ * // Check download progress
172
+ * const progress = steam.workshop.getItemDownloadInfo(itemId);
173
+ * if (progress) {
174
+ * console.log(`Downloading: ${progress.percentComplete}%`);
175
+ * }
176
+ * }
177
+ * ```
178
+ */
179
+ async subscribeItem(publishedFileId) {
180
+ if (!this.apiCore.isInitialized()) {
181
+ console.error('[Steamworks] Steam API not initialized');
182
+ return false;
183
+ }
184
+ const ugc = this.apiCore.getUGCInterface();
185
+ if (!ugc) {
186
+ console.error('[Steamworks] ISteamUGC interface not available');
187
+ return false;
188
+ }
189
+ try {
190
+ const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_SubscribeItem(ugc, publishedFileId);
191
+ if (callHandle === BigInt(0)) {
192
+ console.error('[Steamworks] Failed to initiate item subscription');
193
+ return false;
194
+ }
195
+ // Wait for the callback result
196
+ const result = await this.callbackPoller.poll(BigInt(callHandle), RemoteStorageSubscribePublishedFileResult_t, callbackTypes_1.K_I_REMOTE_STORAGE_SUBSCRIBE_PUBLISHED_FILE_RESULT, 50, // max retries (5 seconds total)
197
+ 100 // delay ms
198
+ );
199
+ if (result && result.m_eResult === 1) { // k_EResultOK = 1
200
+ return true;
201
+ }
202
+ console.error(`[Steamworks] Subscribe item failed with result: ${result?.m_eResult || 'unknown'}`);
203
+ return false;
204
+ }
205
+ catch (error) {
206
+ console.error('[Steamworks] Error subscribing to item:', error);
207
+ return false;
208
+ }
209
+ } /**
210
+ * Unsubscribes from a Workshop item
211
+ *
212
+ * @param publishedFileId - The Workshop item ID to unsubscribe from
213
+ * @returns True if unsubscription was successful, false otherwise
214
+ *
215
+ * @remarks
216
+ * - Item will be uninstalled after the game quits
217
+ *
218
+ * @example
219
+ * ```typescript
220
+ * const itemId = BigInt('123456789');
221
+ * const success = await steam.workshop.unsubscribeItem(itemId);
222
+ *
223
+ * if (success) {
224
+ * console.log('Successfully unsubscribed!');
225
+ * }
226
+ * ```
227
+ */
228
+ async unsubscribeItem(publishedFileId) {
229
+ if (!this.apiCore.isInitialized()) {
230
+ console.error('[Steamworks] Steam API not initialized');
231
+ return false;
232
+ }
233
+ const ugc = this.apiCore.getUGCInterface();
234
+ if (!ugc) {
235
+ console.error('[Steamworks] ISteamUGC interface not available');
236
+ return false;
237
+ }
238
+ try {
239
+ const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_UnsubscribeItem(ugc, publishedFileId);
240
+ if (callHandle === BigInt(0)) {
241
+ console.error('[Steamworks] Failed to initiate item unsubscription');
242
+ return false;
243
+ }
244
+ // Wait for the callback result
245
+ const result = await this.callbackPoller.poll(BigInt(callHandle), RemoteStorageUnsubscribePublishedFileResult_t, callbackTypes_1.K_I_REMOTE_STORAGE_UNSUBSCRIBE_PUBLISHED_FILE_RESULT, 50, // max retries (5 seconds total)
246
+ 100 // delay ms
247
+ );
248
+ if (result && result.m_eResult === 1) { // k_EResultOK = 1
249
+ return true;
250
+ }
251
+ console.error(`[Steamworks] Unsubscribe item failed with result: ${result?.m_eResult || 'unknown'}`);
252
+ return false;
253
+ }
254
+ catch (error) {
255
+ console.error('[Steamworks] Error unsubscribing from item:', error);
256
+ return false;
257
+ }
258
+ }
259
+ /**
260
+ * Gets the number of subscribed Workshop items
261
+ *
262
+ * @returns Number of subscribed items
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * const count = steam.workshop.getNumSubscribedItems();
267
+ * console.log(`Subscribed to ${count} Workshop items`);
268
+ * ```
269
+ */
270
+ getNumSubscribedItems() {
271
+ if (!this.apiCore.isInitialized()) {
272
+ return 0;
273
+ }
274
+ const ugc = this.apiCore.getUGCInterface();
275
+ if (!ugc) {
276
+ return 0;
277
+ }
278
+ try {
279
+ return this.libraryLoader.SteamAPI_ISteamUGC_GetNumSubscribedItems(ugc);
280
+ }
281
+ catch (error) {
282
+ console.error('[Steamworks] Error getting subscribed items count:', error);
283
+ return 0;
284
+ }
285
+ }
286
+ /**
287
+ * Gets all subscribed Workshop item IDs
288
+ *
289
+ * @returns Array of subscribed Workshop item IDs
290
+ *
291
+ * @example
292
+ * ```typescript
293
+ * const items = steam.workshop.getSubscribedItems();
294
+ * items.forEach(itemId => {
295
+ * console.log(`Subscribed to item: ${itemId}`);
296
+ * const info = steam.workshop.getItemInstallInfo(itemId);
297
+ * if (info) {
298
+ * console.log(` Installed at: ${info.folder}`);
299
+ * }
300
+ * });
301
+ * ```
302
+ */
303
+ getSubscribedItems() {
304
+ if (!this.apiCore.isInitialized()) {
305
+ return [];
306
+ }
307
+ const ugc = this.apiCore.getUGCInterface();
308
+ if (!ugc) {
309
+ return [];
310
+ }
311
+ try {
312
+ const count = this.getNumSubscribedItems();
313
+ if (count === 0) {
314
+ return [];
315
+ }
316
+ // Allocate array for item IDs
317
+ const itemsBuffer = Buffer.alloc(count * 8); // uint64 array
318
+ const numReturned = this.libraryLoader.SteamAPI_ISteamUGC_GetSubscribedItems(ugc, itemsBuffer, count);
319
+ // Read the item IDs from buffer
320
+ const items = [];
321
+ for (let i = 0; i < numReturned; i++) {
322
+ items.push(itemsBuffer.readBigUInt64LE(i * 8));
323
+ }
324
+ return items;
325
+ }
326
+ catch (error) {
327
+ console.error('[Steamworks] Error getting subscribed items:', error);
328
+ return [];
329
+ }
330
+ }
331
+ // ========================================
332
+ // Item State and Information
333
+ // ========================================
334
+ /**
335
+ * Gets the state flags for a Workshop item
336
+ *
337
+ * @param publishedFileId - The Workshop item ID
338
+ * @returns State flags (combination of EItemState values)
339
+ *
340
+ * @remarks
341
+ * Use bitwise operations to check for specific states:
342
+ * - EItemState.Subscribed - User is subscribed
343
+ * - EItemState.Installed - Item is installed
344
+ * - EItemState.Downloading - Currently downloading
345
+ * - EItemState.NeedsUpdate - Update available
346
+ *
347
+ * @example
348
+ * ```typescript
349
+ * const itemId = BigInt('123456789');
350
+ * const state = steam.workshop.getItemState(itemId);
351
+ *
352
+ * if (state & EItemState.Subscribed) {
353
+ * console.log('Subscribed');
354
+ * }
355
+ * if (state & EItemState.Installed) {
356
+ * console.log('Installed');
357
+ * }
358
+ * if (state & EItemState.Downloading) {
359
+ * console.log('Downloading...');
360
+ * }
361
+ * if (state & EItemState.NeedsUpdate) {
362
+ * console.log('Update available');
363
+ * }
364
+ * ```
365
+ */
366
+ getItemState(publishedFileId) {
367
+ if (!this.apiCore.isInitialized()) {
368
+ return types_1.EItemState.None;
369
+ }
370
+ const ugc = this.apiCore.getUGCInterface();
371
+ if (!ugc) {
372
+ return types_1.EItemState.None;
373
+ }
374
+ try {
375
+ return this.libraryLoader.SteamAPI_ISteamUGC_GetItemState(ugc, publishedFileId);
376
+ }
377
+ catch (error) {
378
+ console.error('[Steamworks] Error getting item state:', error);
379
+ return types_1.EItemState.None;
380
+ }
381
+ }
382
+ /**
383
+ * Gets installation information for a Workshop item
384
+ *
385
+ * @param publishedFileId - The Workshop item ID
386
+ * @returns Installation info or null if not installed
387
+ *
388
+ * @remarks
389
+ * Only returns data if item has EItemState.Installed flag set
390
+ *
391
+ * @example
392
+ * ```typescript
393
+ * const itemId = BigInt('123456789');
394
+ * const info = steam.workshop.getItemInstallInfo(itemId);
395
+ *
396
+ * if (info) {
397
+ * console.log(`Installed at: ${info.folder}`);
398
+ * console.log(`Size on disk: ${info.sizeOnDisk} bytes`);
399
+ * console.log(`Install time: ${new Date(info.timestamp * 1000)}`);
400
+ * }
401
+ * ```
402
+ */
403
+ getItemInstallInfo(publishedFileId) {
404
+ if (!this.apiCore.isInitialized()) {
405
+ return null;
406
+ }
407
+ const ugc = this.apiCore.getUGCInterface();
408
+ if (!ugc) {
409
+ return null;
410
+ }
411
+ try {
412
+ const sizeBuffer = Buffer.alloc(8); // uint64
413
+ const folderBuffer = Buffer.alloc(4096); // path buffer
414
+ const timestampBuffer = Buffer.alloc(4); // uint32
415
+ const success = this.libraryLoader.SteamAPI_ISteamUGC_GetItemInstallInfo(ugc, publishedFileId, sizeBuffer, folderBuffer, 4096, timestampBuffer);
416
+ if (!success) {
417
+ return null;
418
+ }
419
+ // Read folder path (null-terminated string)
420
+ const folderEnd = folderBuffer.indexOf(0);
421
+ const folder = folderBuffer.toString('utf8', 0, folderEnd > 0 ? folderEnd : 4096);
422
+ return {
423
+ sizeOnDisk: sizeBuffer.readBigUInt64LE(0),
424
+ folder: folder,
425
+ timestamp: timestampBuffer.readUInt32LE(0)
426
+ };
427
+ }
428
+ catch (error) {
429
+ console.error('[Steamworks] Error getting item install info:', error);
430
+ return null;
431
+ }
432
+ }
433
+ /**
434
+ * Gets download progress for a Workshop item
435
+ *
436
+ * @param publishedFileId - The Workshop item ID
437
+ * @returns Download info or null if not downloading
438
+ *
439
+ * @remarks
440
+ * Only returns data if item has EItemState.Downloading flag set
441
+ *
442
+ * @example
443
+ * ```typescript
444
+ * const itemId = BigInt('123456789');
445
+ * const progress = steam.workshop.getItemDownloadInfo(itemId);
446
+ *
447
+ * if (progress) {
448
+ * console.log(`Download: ${progress.percentComplete.toFixed(1)}%`);
449
+ * console.log(`${progress.bytesDownloaded} / ${progress.bytesTotal} bytes`);
450
+ * }
451
+ * ```
452
+ */
453
+ getItemDownloadInfo(publishedFileId) {
454
+ if (!this.apiCore.isInitialized()) {
455
+ return null;
456
+ }
457
+ const ugc = this.apiCore.getUGCInterface();
458
+ if (!ugc) {
459
+ return null;
460
+ }
461
+ try {
462
+ const downloadedBuffer = Buffer.alloc(8); // uint64
463
+ const totalBuffer = Buffer.alloc(8); // uint64
464
+ const success = this.libraryLoader.SteamAPI_ISteamUGC_GetItemDownloadInfo(ugc, publishedFileId, downloadedBuffer, totalBuffer);
465
+ if (!success) {
466
+ return null;
467
+ }
468
+ const bytesDownloaded = downloadedBuffer.readBigUInt64LE(0);
469
+ const bytesTotal = totalBuffer.readBigUInt64LE(0);
470
+ const percentComplete = bytesTotal > BigInt(0)
471
+ ? Number((bytesDownloaded * BigInt(100)) / bytesTotal)
472
+ : 0;
473
+ return {
474
+ bytesDownloaded,
475
+ bytesTotal,
476
+ percentComplete
477
+ };
478
+ }
479
+ catch (error) {
480
+ console.error('[Steamworks] Error getting item download info:', error);
481
+ return null;
482
+ }
483
+ }
484
+ /**
485
+ * Downloads a Workshop item (forces download if not subscribed)
486
+ *
487
+ * @param publishedFileId - The Workshop item ID
488
+ * @param highPriority - If true, suspends other downloads and prioritizes this one
489
+ * @returns True if download started, false otherwise
490
+ *
491
+ * @remarks
492
+ * - If item is already installed, files should not be used until callback received
493
+ * - Listen for DownloadItemResult_t callback for completion
494
+ * - If not subscribed, item will be cached temporarily
495
+ *
496
+ * @example
497
+ * ```typescript
498
+ * const itemId = BigInt('123456789');
499
+ * const started = steam.workshop.downloadItem(itemId, true);
500
+ *
501
+ * if (started) {
502
+ * console.log('Download started');
503
+ * // Poll getItemDownloadInfo() for progress
504
+ * }
505
+ * ```
506
+ */
507
+ downloadItem(publishedFileId, highPriority = false) {
508
+ if (!this.apiCore.isInitialized()) {
509
+ return false;
510
+ }
511
+ const ugc = this.apiCore.getUGCInterface();
512
+ if (!ugc) {
513
+ return false;
514
+ }
515
+ try {
516
+ return this.libraryLoader.SteamAPI_ISteamUGC_DownloadItem(ugc, publishedFileId, highPriority);
517
+ }
518
+ catch (error) {
519
+ console.error('[Steamworks] Error downloading item:', error);
520
+ return false;
521
+ }
522
+ }
523
+ // ========================================
524
+ // Query Operations
525
+ // ========================================
526
+ /**
527
+ * Creates a query for a user's Workshop items
528
+ *
529
+ * @param accountId - Steam account ID (32-bit, lower part of SteamID)
530
+ * @param listType - Type of list to query
531
+ * @param matchingType - Type of UGC to match
532
+ * @param sortOrder - How to sort results
533
+ * @param creatorAppId - App that created the items
534
+ * @param consumerAppId - App that will consume the items
535
+ * @param page - Page number (1-based)
536
+ * @returns Query handle for use with sendQueryUGCRequest
537
+ *
538
+ * @example
539
+ * ```typescript
540
+ * // Query current user's published items
541
+ * const accountId = 12345678; // Get from user's SteamID
542
+ * const query = steam.workshop.createQueryUserUGCRequest(
543
+ * accountId,
544
+ * EUserUGCList.Published,
545
+ * EUGCMatchingUGCType.Items,
546
+ * EUserUGCListSortOrder.CreationOrderDesc,
547
+ * 480, // Spacewar
548
+ * 480,
549
+ * 1 // First page
550
+ * );
551
+ *
552
+ * steam.workshop.sendQueryUGCRequest(query);
553
+ * // Wait for SteamUGCQueryCompleted_t callback
554
+ * ```
555
+ */
556
+ createQueryUserUGCRequest(accountId, listType, matchingType, sortOrder, creatorAppId, consumerAppId, page) {
557
+ if (!this.apiCore.isInitialized()) {
558
+ return types_1.K_UGC_QUERY_HANDLE_INVALID;
559
+ }
560
+ const ugc = this.apiCore.getUGCInterface();
561
+ if (!ugc) {
562
+ return types_1.K_UGC_QUERY_HANDLE_INVALID;
563
+ }
564
+ try {
565
+ const handle = this.libraryLoader.SteamAPI_ISteamUGC_CreateQueryUserUGCRequest(ugc, accountId, listType, matchingType, sortOrder, creatorAppId, consumerAppId, page);
566
+ return BigInt(handle);
567
+ }
568
+ catch (error) {
569
+ console.error('[Steamworks] Error creating user UGC query:', error);
570
+ return types_1.K_UGC_QUERY_HANDLE_INVALID;
571
+ }
572
+ }
573
+ /**
574
+ * Creates a query for all Workshop items
575
+ *
576
+ * @param queryType - Type of query/sorting
577
+ * @param matchingType - Type of UGC to match
578
+ * @param creatorAppId - App that created the items
579
+ * @param consumerAppId - App that will consume the items
580
+ * @param page - Page number (1-based)
581
+ * @returns Query handle for use with sendQueryUGCRequest
582
+ *
583
+ * @example
584
+ * ```typescript
585
+ * // Query most popular items
586
+ * const query = steam.workshop.createQueryAllUGCRequest(
587
+ * EUGCQuery.RankedByVote,
588
+ * EUGCMatchingUGCType.Items,
589
+ * 480, // Spacewar
590
+ * 480,
591
+ * 1 // First page
592
+ * );
593
+ *
594
+ * steam.workshop.sendQueryUGCRequest(query);
595
+ * // Wait for SteamUGCQueryCompleted_t callback
596
+ * ```
597
+ */
598
+ createQueryAllUGCRequest(queryType, matchingType, creatorAppId, consumerAppId, page) {
599
+ if (!this.apiCore.isInitialized()) {
600
+ return types_1.K_UGC_QUERY_HANDLE_INVALID;
601
+ }
602
+ const ugc = this.apiCore.getUGCInterface();
603
+ if (!ugc) {
604
+ return types_1.K_UGC_QUERY_HANDLE_INVALID;
605
+ }
606
+ try {
607
+ const handle = this.libraryLoader.SteamAPI_ISteamUGC_CreateQueryAllUGCRequestPage(ugc, queryType, matchingType, creatorAppId, consumerAppId, page);
608
+ return BigInt(handle);
609
+ }
610
+ catch (error) {
611
+ console.error('[Steamworks] Error creating all UGC query:', error);
612
+ return types_1.K_UGC_QUERY_HANDLE_INVALID;
613
+ }
614
+ }
615
+ /**
616
+ * Sends a UGC query to Steam and waits for results
617
+ *
618
+ * @param queryHandle - Handle from createQueryUserUGCRequest or createQueryAllUGCRequest
619
+ * @returns Query result with number of results and metadata, or null if failed
620
+ *
621
+ * @remarks
622
+ * After processing results, call releaseQueryUGCRequest to free memory
623
+ * Use getQueryUGCResult to retrieve individual item details
624
+ *
625
+ * @example
626
+ * ```typescript
627
+ * const query = steam.workshop.createQueryAllUGCRequest(...);
628
+ * const result = await steam.workshop.sendQueryUGCRequest(query);
629
+ *
630
+ * if (result) {
631
+ * console.log(`Found ${result.numResults} items (${result.totalResults} total)`);
632
+ *
633
+ * // Get individual results
634
+ * for (let i = 0; i < result.numResults; i++) {
635
+ * const item = steam.workshop.getQueryUGCResult(query, i);
636
+ * if (item) {
637
+ * console.log(`Item: ${item.title}`);
638
+ * }
639
+ * }
640
+ *
641
+ * // Clean up
642
+ * steam.workshop.releaseQueryUGCRequest(query);
643
+ * }
644
+ * ```
645
+ */
646
+ async sendQueryUGCRequest(queryHandle) {
647
+ if (!this.apiCore.isInitialized()) {
648
+ console.error('[Steamworks] Steam API not initialized');
649
+ return null;
650
+ }
651
+ const ugc = this.apiCore.getUGCInterface();
652
+ if (!ugc) {
653
+ console.error('[Steamworks] ISteamUGC interface not available');
654
+ return null;
655
+ }
656
+ try {
657
+ const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_SendQueryUGCRequest(ugc, queryHandle);
658
+ if (callHandle === BigInt(0)) {
659
+ console.error('[Steamworks] Failed to initiate UGC query');
660
+ return null;
661
+ }
662
+ // Wait for the callback result
663
+ const result = await this.callbackPoller.poll(BigInt(callHandle), SteamUGCQueryCompleted_t, callbackTypes_1.K_I_STEAM_UGC_QUERY_COMPLETED, 600, // max retries (60 seconds total)
664
+ 100 // delay ms
665
+ );
666
+ if (result && result.m_eResult === 1) { // k_EResultOK = 1
667
+ return {
668
+ numResults: result.m_unNumResultsReturned,
669
+ totalResults: result.m_unTotalMatchingResults,
670
+ cachedData: result.m_bCachedData
671
+ };
672
+ }
673
+ console.error(`[Steamworks] UGC query failed with result: ${result?.m_eResult || 'unknown'}`);
674
+ return null;
675
+ }
676
+ catch (error) {
677
+ console.error('[Steamworks] Error sending UGC query:', error);
678
+ return null;
679
+ }
680
+ }
681
+ /**
682
+ * Gets a single result from a completed UGC query
683
+ *
684
+ * @param queryHandle - Handle from a completed query
685
+ * @param index - Index of the result to retrieve (0-based)
686
+ * @returns Workshop item details or null if failed
687
+ *
688
+ * @remarks
689
+ * Call this after receiving SteamUGCQueryCompleted_t callback
690
+ * Index must be less than the number of results returned in callback
691
+ *
692
+ * @example
693
+ * ```typescript
694
+ * // After query completes via callback:
695
+ * const query = steam.workshop.createQueryAllUGCRequest(...);
696
+ * steam.workshop.sendQueryUGCRequest(query);
697
+ *
698
+ * // In callback handler when query completes:
699
+ * // Get first 10 results
700
+ * for (let i = 0; i < 10; i++) {
701
+ * const item = steam.workshop.getQueryUGCResult(query, i);
702
+ * if (item) {
703
+ * console.log(`${item.title} by ${item.steamIdOwner}`);
704
+ * console.log(`Votes: ${item.votesUp} up, ${item.votesDown} down`);
705
+ * }
706
+ * }
707
+ *
708
+ * steam.workshop.releaseQueryUGCRequest(query);
709
+ * ```
710
+ */
711
+ getQueryUGCResult(queryHandle, index) {
712
+ if (!this.apiCore.isInitialized()) {
713
+ return null;
714
+ }
715
+ const ugc = this.apiCore.getUGCInterface();
716
+ if (!ugc) {
717
+ return null;
718
+ }
719
+ try {
720
+ // Allocate buffer for SteamUGCDetails_t struct
721
+ // Struct size: ~10KB (8KB description + 1KB tags + other fields)
722
+ const detailsBuffer = Buffer.alloc(10240);
723
+ const success = this.libraryLoader.SteamAPI_ISteamUGC_GetQueryUGCResult(ugc, queryHandle, index, detailsBuffer);
724
+ if (!success) {
725
+ return null;
726
+ }
727
+ // Parse the SteamUGCDetails_t struct
728
+ let offset = 0;
729
+ // PublishedFileId_t m_nPublishedFileId (uint64)
730
+ const publishedFileId = detailsBuffer.readBigUInt64LE(offset);
731
+ offset += 8;
732
+ // EResult m_eResult (int32)
733
+ const result = detailsBuffer.readInt32LE(offset);
734
+ offset += 4;
735
+ // EWorkshopFileType m_eFileType (int32)
736
+ const fileType = detailsBuffer.readInt32LE(offset);
737
+ offset += 4;
738
+ // AppId_t m_nCreatorAppID (uint32)
739
+ const creatorAppId = detailsBuffer.readUInt32LE(offset);
740
+ offset += 4;
741
+ // AppId_t m_nConsumerAppID (uint32)
742
+ const consumerAppId = detailsBuffer.readUInt32LE(offset);
743
+ offset += 4;
744
+ // char m_rgchTitle[k_cchPublishedDocumentTitleMax] (129 bytes)
745
+ const titleEnd = detailsBuffer.indexOf(0, offset);
746
+ const title = detailsBuffer.toString('utf8', offset, titleEnd > offset ? titleEnd : offset + 129);
747
+ offset += 129;
748
+ // char m_rgchDescription[k_cchPublishedDocumentDescriptionMax] (8000 bytes)
749
+ const descEnd = detailsBuffer.indexOf(0, offset);
750
+ const description = detailsBuffer.toString('utf8', offset, descEnd > offset ? descEnd : offset + 8000);
751
+ offset += 8000;
752
+ // uint64 m_ulSteamIDOwner
753
+ const steamIdOwner = detailsBuffer.readBigUInt64LE(offset);
754
+ offset += 8;
755
+ // uint32 m_rtimeCreated
756
+ const timeCreated = detailsBuffer.readUInt32LE(offset);
757
+ offset += 4;
758
+ // uint32 m_rtimeUpdated
759
+ const timeUpdated = detailsBuffer.readUInt32LE(offset);
760
+ offset += 4;
761
+ // uint32 m_rtimeAddedToUserList
762
+ const timeAddedToUserList = detailsBuffer.readUInt32LE(offset);
763
+ offset += 4;
764
+ // ERemoteStoragePublishedFileVisibility m_eVisibility (int32)
765
+ const visibility = detailsBuffer.readInt32LE(offset);
766
+ offset += 4;
767
+ // bool m_bBanned
768
+ const banned = detailsBuffer.readUInt8(offset) !== 0;
769
+ offset += 1;
770
+ // bool m_bAcceptedForUse
771
+ const acceptedForUse = detailsBuffer.readUInt8(offset) !== 0;
772
+ offset += 1;
773
+ // bool m_bTagsTruncated
774
+ const tagsTruncated = detailsBuffer.readUInt8(offset) !== 0;
775
+ offset += 1;
776
+ // Padding alignment (structs are aligned)
777
+ offset += 1; // Align to 4 bytes
778
+ // char m_rgchTags[k_cchTagListMax] (1025 bytes)
779
+ const tagsEnd = detailsBuffer.indexOf(0, offset);
780
+ const tagsString = detailsBuffer.toString('utf8', offset, tagsEnd > offset ? tagsEnd : offset + 1025);
781
+ const tags = tagsString.split(',').map(t => t.trim()).filter(t => t.length > 0);
782
+ offset += 1025;
783
+ // Padding alignment
784
+ offset += 3; // Align to 8 bytes
785
+ // UGCHandle_t m_hFile (uint64)
786
+ const fileHandle = detailsBuffer.readBigUInt64LE(offset);
787
+ offset += 8;
788
+ // UGCHandle_t m_hPreviewFile (uint64)
789
+ const previewFileHandle = detailsBuffer.readBigUInt64LE(offset);
790
+ offset += 8;
791
+ // char m_pchFileName[k_cchFilenameMax] (260 bytes)
792
+ const fileNameEnd = detailsBuffer.indexOf(0, offset);
793
+ const fileName = detailsBuffer.toString('utf8', offset, fileNameEnd > offset ? fileNameEnd : offset + 260);
794
+ offset += 260;
795
+ // int32 m_nFileSize
796
+ const fileSize = detailsBuffer.readInt32LE(offset);
797
+ offset += 4;
798
+ // int32 m_nPreviewFileSize
799
+ const previewFileSize = detailsBuffer.readInt32LE(offset);
800
+ offset += 4;
801
+ // char m_rgchURL[k_cchPublishedFileURLMax] (256 bytes)
802
+ const urlEnd = detailsBuffer.indexOf(0, offset);
803
+ const url = detailsBuffer.toString('utf8', offset, urlEnd > offset ? urlEnd : offset + 256);
804
+ offset += 256;
805
+ // uint32 m_unVotesUp
806
+ const votesUp = detailsBuffer.readUInt32LE(offset);
807
+ offset += 4;
808
+ // uint32 m_unVotesDown
809
+ const votesDown = detailsBuffer.readUInt32LE(offset);
810
+ offset += 4;
811
+ // float m_flScore
812
+ const score = detailsBuffer.readFloatLE(offset);
813
+ offset += 4;
814
+ // uint32 m_unNumChildren
815
+ const numChildren = detailsBuffer.readUInt32LE(offset);
816
+ offset += 4;
817
+ // uint64 m_ulTotalFilesSize
818
+ const totalFilesSize = detailsBuffer.readBigUInt64LE(offset);
819
+ return {
820
+ publishedFileId,
821
+ result,
822
+ fileType,
823
+ creatorAppID: creatorAppId,
824
+ consumerAppID: consumerAppId,
825
+ title,
826
+ description,
827
+ steamIDOwner: steamIdOwner,
828
+ timeCreated,
829
+ timeUpdated,
830
+ timeAddedToUserList,
831
+ visibility,
832
+ banned,
833
+ acceptedForUse,
834
+ tagsTruncated,
835
+ tags,
836
+ file: fileHandle,
837
+ previewFile: previewFileHandle,
838
+ fileName,
839
+ fileSize,
840
+ previewFileSize,
841
+ url,
842
+ votesUp,
843
+ votesDown,
844
+ score,
845
+ numChildren,
846
+ totalFilesSize
847
+ };
848
+ }
849
+ catch (error) {
850
+ console.error('[Steamworks] Error getting query UGC result:', error);
851
+ return null;
852
+ }
853
+ }
854
+ /**
855
+ * Releases a query handle and frees associated memory
856
+ *
857
+ * @param queryHandle - Handle to release
858
+ * @returns True if released successfully
859
+ *
860
+ * @remarks
861
+ * Always call this after processing query results to prevent memory leaks
862
+ *
863
+ * @example
864
+ * ```typescript
865
+ * const query = steam.workshop.createQueryAllUGCRequest(...);
866
+ * steam.workshop.sendQueryUGCRequest(query);
867
+ * // After callback and processing results:
868
+ * steam.workshop.releaseQueryUGCRequest(query);
869
+ * ```
870
+ */
871
+ releaseQueryUGCRequest(queryHandle) {
872
+ if (!this.apiCore.isInitialized()) {
873
+ return false;
874
+ }
875
+ const ugc = this.apiCore.getUGCInterface();
876
+ if (!ugc) {
877
+ return false;
878
+ }
879
+ try {
880
+ return this.libraryLoader.SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(ugc, queryHandle);
881
+ }
882
+ catch (error) {
883
+ console.error('[Steamworks] Error releasing UGC query:', error);
884
+ return false;
885
+ }
886
+ }
887
+ // ========================================
888
+ // Item Creation and Update
889
+ // ========================================
890
+ /**
891
+ * Creates a new Workshop item and waits for the result
892
+ *
893
+ * @param consumerAppId - App ID that will consume this item
894
+ * @param fileType - Type of Workshop file
895
+ * @returns The published file ID of the newly created item, or null if failed
896
+ *
897
+ * @remarks
898
+ * This method automatically waits for the CreateItemResult_t callback.
899
+ * Once created, use startItemUpdate to set content and properties.
900
+ * Blocks until result is ready or times out (default 5 seconds).
901
+ *
902
+ * @example
903
+ * ```typescript
904
+ * const publishedFileId = await steam.workshop.createItem(
905
+ * 480,
906
+ * EWorkshopFileType.Community
907
+ * );
908
+ *
909
+ * if (publishedFileId) {
910
+ * console.log(`Created item with ID: ${publishedFileId}`);
911
+ *
912
+ * // Now you can update the item
913
+ * const updateHandle = steam.workshop.startItemUpdate(480, publishedFileId);
914
+ * steam.workshop.setItemTitle(updateHandle, 'My Awesome Mod');
915
+ * steam.workshop.setItemContent(updateHandle, '/path/to/content');
916
+ * steam.workshop.submitItemUpdate(updateHandle, 'Initial release');
917
+ * }
918
+ * ```
919
+ */
920
+ async createItem(consumerAppId, fileType) {
921
+ if (!this.apiCore.isInitialized()) {
922
+ console.error('[Steamworks] Steam API not initialized');
923
+ return null;
924
+ }
925
+ const ugc = this.apiCore.getUGCInterface();
926
+ if (!ugc) {
927
+ console.error('[Steamworks] ISteamUGC interface not available');
928
+ return null;
929
+ }
930
+ try {
931
+ // Call Steam API to create the item
932
+ const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_CreateItem(ugc, consumerAppId, fileType);
933
+ if (callHandle === BigInt(0)) {
934
+ console.error('[Steamworks] Failed to initiate item creation');
935
+ return null;
936
+ }
937
+ // Wait for the callback result (longer timeout for item creation)
938
+ const result = await this.callbackPoller.poll(BigInt(callHandle), CreateItemResult_t, callbackTypes_1.K_I_CREATE_ITEM_RESULT, 600, // max retries (60 seconds total - item creation can be slow)
939
+ 100 // delay ms
940
+ );
941
+ if (result && result.m_eResult === 1) { // k_EResultOK = 1
942
+ if (result.m_bUserNeedsToAcceptWorkshopLegalAgreement) {
943
+ console.warn('[Steamworks] User needs to accept Workshop Legal Agreement');
944
+ console.warn('[Steamworks] Visit: https://steamcommunity.com/sharedfiles/workshoplegalagreement');
945
+ }
946
+ return BigInt(result.m_nPublishedFileId);
947
+ }
948
+ console.error(`[Steamworks] Create item failed with result: ${result?.m_eResult || 'unknown'}`);
949
+ return null;
950
+ }
951
+ catch (error) {
952
+ console.error('[Steamworks] Error creating item:', error);
953
+ return null;
954
+ }
955
+ }
956
+ /**
957
+ * Starts an update for a Workshop item
958
+ *
959
+ * @param consumerAppId - App ID
960
+ * @param publishedFileId - Workshop item ID to update
961
+ * @returns Update handle for use with set* functions
962
+ *
963
+ * @remarks
964
+ * Call set* functions to modify properties, then submitItemUpdate to commit
965
+ *
966
+ * @example
967
+ * ```typescript
968
+ * const itemId = BigInt('123456789');
969
+ * const updateHandle = steam.workshop.startItemUpdate(480, itemId);
970
+ *
971
+ * steam.workshop.setItemTitle(updateHandle, 'My Awesome Mod v2.0');
972
+ * steam.workshop.setItemDescription(updateHandle, 'Updated with new features!');
973
+ * steam.workshop.setItemContent(updateHandle, '/path/to/mod/folder');
974
+ *
975
+ * steam.workshop.submitItemUpdate(updateHandle, 'Version 2.0 changelog');
976
+ * ```
977
+ */
978
+ startItemUpdate(consumerAppId, publishedFileId) {
979
+ if (!this.apiCore.isInitialized()) {
980
+ return types_1.K_UGC_UPDATE_HANDLE_INVALID;
981
+ }
982
+ const ugc = this.apiCore.getUGCInterface();
983
+ if (!ugc) {
984
+ return types_1.K_UGC_UPDATE_HANDLE_INVALID;
985
+ }
986
+ try {
987
+ const handle = this.libraryLoader.SteamAPI_ISteamUGC_StartItemUpdate(ugc, consumerAppId, publishedFileId);
988
+ return BigInt(handle);
989
+ }
990
+ catch (error) {
991
+ console.error('[Steamworks] Error starting item update:', error);
992
+ return types_1.K_UGC_UPDATE_HANDLE_INVALID;
993
+ }
994
+ }
995
+ /**
996
+ * Sets the title of a Workshop item being updated
997
+ *
998
+ * @param updateHandle - Handle from startItemUpdate
999
+ * @param title - New title for the item
1000
+ * @returns True if set successfully
1001
+ */
1002
+ setItemTitle(updateHandle, title) {
1003
+ if (!this.apiCore.isInitialized()) {
1004
+ return false;
1005
+ }
1006
+ const ugc = this.apiCore.getUGCInterface();
1007
+ if (!ugc) {
1008
+ return false;
1009
+ }
1010
+ try {
1011
+ return this.libraryLoader.SteamAPI_ISteamUGC_SetItemTitle(ugc, updateHandle, title);
1012
+ }
1013
+ catch (error) {
1014
+ console.error('[Steamworks] Error setting item title:', error);
1015
+ return false;
1016
+ }
1017
+ }
1018
+ /**
1019
+ * Sets the description of a Workshop item being updated
1020
+ *
1021
+ * @param updateHandle - Handle from startItemUpdate
1022
+ * @param description - New description for the item
1023
+ * @returns True if set successfully
1024
+ */
1025
+ setItemDescription(updateHandle, description) {
1026
+ if (!this.apiCore.isInitialized()) {
1027
+ return false;
1028
+ }
1029
+ const ugc = this.apiCore.getUGCInterface();
1030
+ if (!ugc) {
1031
+ return false;
1032
+ }
1033
+ try {
1034
+ return this.libraryLoader.SteamAPI_ISteamUGC_SetItemDescription(ugc, updateHandle, description);
1035
+ }
1036
+ catch (error) {
1037
+ console.error('[Steamworks] Error setting item description:', error);
1038
+ return false;
1039
+ }
1040
+ }
1041
+ /**
1042
+ * Sets the visibility of a Workshop item being updated
1043
+ *
1044
+ * @param updateHandle - Handle from startItemUpdate
1045
+ * @param visibility - Visibility setting
1046
+ * @returns True if set successfully
1047
+ */
1048
+ setItemVisibility(updateHandle, visibility) {
1049
+ if (!this.apiCore.isInitialized()) {
1050
+ return false;
1051
+ }
1052
+ const ugc = this.apiCore.getUGCInterface();
1053
+ if (!ugc) {
1054
+ return false;
1055
+ }
1056
+ try {
1057
+ return this.libraryLoader.SteamAPI_ISteamUGC_SetItemVisibility(ugc, updateHandle, visibility);
1058
+ }
1059
+ catch (error) {
1060
+ console.error('[Steamworks] Error setting item visibility:', error);
1061
+ return false;
1062
+ }
1063
+ }
1064
+ /**
1065
+ * Sets the content folder for a Workshop item being updated
1066
+ *
1067
+ * @param updateHandle - Handle from startItemUpdate
1068
+ * @param contentFolder - Path to folder containing item content
1069
+ * @returns True if set successfully
1070
+ *
1071
+ * @remarks
1072
+ * All files in the folder will be uploaded to Workshop
1073
+ */
1074
+ setItemContent(updateHandle, contentFolder) {
1075
+ if (!this.apiCore.isInitialized()) {
1076
+ return false;
1077
+ }
1078
+ const ugc = this.apiCore.getUGCInterface();
1079
+ if (!ugc) {
1080
+ return false;
1081
+ }
1082
+ try {
1083
+ return this.libraryLoader.SteamAPI_ISteamUGC_SetItemContent(ugc, updateHandle, contentFolder);
1084
+ }
1085
+ catch (error) {
1086
+ console.error('[Steamworks] Error setting item content:', error);
1087
+ return false;
1088
+ }
1089
+ }
1090
+ /**
1091
+ * Sets the preview image for a Workshop item being updated
1092
+ *
1093
+ * @param updateHandle - Handle from startItemUpdate
1094
+ * @param previewFile - Path to preview image file (must be under 1MB)
1095
+ * @returns True if set successfully
1096
+ */
1097
+ setItemPreview(updateHandle, previewFile) {
1098
+ if (!this.apiCore.isInitialized()) {
1099
+ return false;
1100
+ }
1101
+ const ugc = this.apiCore.getUGCInterface();
1102
+ if (!ugc) {
1103
+ return false;
1104
+ }
1105
+ try {
1106
+ return this.libraryLoader.SteamAPI_ISteamUGC_SetItemPreview(ugc, updateHandle, previewFile);
1107
+ }
1108
+ catch (error) {
1109
+ console.error('[Steamworks] Error setting item preview:', error);
1110
+ return false;
1111
+ }
1112
+ }
1113
+ /**
1114
+ * Submits an item update to Steam Workshop and waits for completion
1115
+ *
1116
+ * @param updateHandle - Handle from startItemUpdate
1117
+ * @param changeNote - Description of changes (shown in update history)
1118
+ * @returns True if update was submitted successfully, false otherwise
1119
+ *
1120
+ * @remarks
1121
+ * Use getItemUpdateProgress to track upload progress
1122
+ *
1123
+ * @example
1124
+ * ```typescript
1125
+ * const updateHandle = steam.workshop.startItemUpdate(480, itemId);
1126
+ * steam.workshop.setItemTitle(updateHandle, 'Updated Title');
1127
+ * const success = await steam.workshop.submitItemUpdate(updateHandle, 'Fixed bugs');
1128
+ *
1129
+ * if (success) {
1130
+ * console.log('Update submitted successfully!');
1131
+ * }
1132
+ * ```
1133
+ */
1134
+ async submitItemUpdate(updateHandle, changeNote) {
1135
+ if (!this.apiCore.isInitialized()) {
1136
+ console.error('[Steamworks] Steam API not initialized');
1137
+ return false;
1138
+ }
1139
+ const ugc = this.apiCore.getUGCInterface();
1140
+ if (!ugc) {
1141
+ console.error('[Steamworks] ISteamUGC interface not available');
1142
+ return false;
1143
+ }
1144
+ try {
1145
+ const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_SubmitItemUpdate(ugc, updateHandle, changeNote);
1146
+ if (callHandle === BigInt(0)) {
1147
+ console.error('[Steamworks] Failed to initiate item update submission');
1148
+ return false;
1149
+ }
1150
+ // Wait for the callback result (longer timeout for uploads)
1151
+ const result = await this.callbackPoller.poll(BigInt(callHandle), SubmitItemUpdateResult_t, callbackTypes_1.K_I_SUBMIT_ITEM_UPDATE_RESULT, 300, // max retries (30 seconds total - uploads can be slow)
1152
+ 100 // delay ms
1153
+ );
1154
+ if (result && result.m_eResult === 1) { // k_EResultOK = 1
1155
+ if (result.m_bUserNeedsToAcceptWorkshopLegalAgreement) {
1156
+ console.warn('[Steamworks] User needs to accept Workshop Legal Agreement');
1157
+ console.warn('[Steamworks] Visit: https://steamcommunity.com/sharedfiles/workshoplegalagreement');
1158
+ }
1159
+ return true;
1160
+ }
1161
+ console.error(`[Steamworks] Submit item update failed with result: ${result?.m_eResult || 'timeout'}`);
1162
+ return false;
1163
+ }
1164
+ catch (error) {
1165
+ console.error('[Steamworks] Error submitting item update:', error);
1166
+ return false;
1167
+ }
1168
+ }
1169
+ /**
1170
+ * Gets the progress of an item update submission
1171
+ *
1172
+ * @param updateHandle - Handle from startItemUpdate
1173
+ * @returns Update progress info
1174
+ *
1175
+ * @example
1176
+ * ```typescript
1177
+ * const updateHandle = steam.workshop.startItemUpdate(480, itemId);
1178
+ * // ... set properties and submit ...
1179
+ *
1180
+ * // Poll for progress
1181
+ * const progress = steam.workshop.getItemUpdateProgress(updateHandle);
1182
+ * console.log(`Status: ${EItemUpdateStatus[progress.status]}`);
1183
+ * console.log(`Progress: ${progress.percentComplete.toFixed(1)}%`);
1184
+ * ```
1185
+ */
1186
+ getItemUpdateProgress(updateHandle) {
1187
+ const result = {
1188
+ status: types_1.EItemUpdateStatus.Invalid,
1189
+ bytesProcessed: BigInt(0),
1190
+ bytesTotal: BigInt(0),
1191
+ percentComplete: 0
1192
+ };
1193
+ if (!this.apiCore.isInitialized()) {
1194
+ return result;
1195
+ }
1196
+ const ugc = this.apiCore.getUGCInterface();
1197
+ if (!ugc) {
1198
+ return result;
1199
+ }
1200
+ try {
1201
+ const processedBuffer = Buffer.alloc(8); // uint64
1202
+ const totalBuffer = Buffer.alloc(8); // uint64
1203
+ const status = this.libraryLoader.SteamAPI_ISteamUGC_GetItemUpdateProgress(ugc, updateHandle, processedBuffer, totalBuffer);
1204
+ const bytesProcessed = processedBuffer.readBigUInt64LE(0);
1205
+ const bytesTotal = totalBuffer.readBigUInt64LE(0);
1206
+ const percentComplete = bytesTotal > BigInt(0)
1207
+ ? Number((bytesProcessed * BigInt(100)) / bytesTotal)
1208
+ : 0;
1209
+ result.status = status;
1210
+ result.bytesProcessed = bytesProcessed;
1211
+ result.bytesTotal = bytesTotal;
1212
+ result.percentComplete = percentComplete;
1213
+ return result;
1214
+ }
1215
+ catch (error) {
1216
+ console.error('[Steamworks] Error getting item update progress:', error);
1217
+ return result;
1218
+ }
1219
+ }
1220
+ // ========================================
1221
+ // Voting and Favorites
1222
+ // ========================================
1223
+ /**
1224
+ * Votes on a Workshop item
1225
+ *
1226
+ * @param publishedFileId - Workshop item ID
1227
+ * @param voteUp - True to vote up, false to vote down
1228
+ * @returns True if vote was successful, false otherwise
1229
+ *
1230
+ * @example
1231
+ * ```typescript
1232
+ * const itemId = BigInt('123456789');
1233
+ * const success = await steam.workshop.setUserItemVote(itemId, true); // Vote up
1234
+ *
1235
+ * if (success) {
1236
+ * console.log('Vote registered!');
1237
+ * }
1238
+ * ```
1239
+ */
1240
+ async setUserItemVote(publishedFileId, voteUp) {
1241
+ if (!this.apiCore.isInitialized()) {
1242
+ console.error('[Steamworks] Steam API not initialized');
1243
+ return false;
1244
+ }
1245
+ const ugc = this.apiCore.getUGCInterface();
1246
+ if (!ugc) {
1247
+ console.error('[Steamworks] ISteamUGC interface not available');
1248
+ return false;
1249
+ }
1250
+ try {
1251
+ const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_SetUserItemVote(ugc, publishedFileId, voteUp);
1252
+ if (callHandle === BigInt(0)) {
1253
+ console.error('[Steamworks] Failed to initiate vote');
1254
+ return false;
1255
+ }
1256
+ // Wait for the callback result
1257
+ const result = await this.callbackPoller.poll(BigInt(callHandle), SetUserItemVoteResult_t, callbackTypes_1.K_I_SET_USER_ITEM_VOTE_RESULT, 50, // max retries (5 seconds total)
1258
+ 100 // delay ms
1259
+ );
1260
+ if (result && result.m_eResult === 1) { // k_EResultOK = 1
1261
+ return true;
1262
+ }
1263
+ console.error(`[Steamworks] Set user item vote failed with result: ${result?.m_eResult || 'unknown'}`);
1264
+ return false;
1265
+ }
1266
+ catch (error) {
1267
+ console.error('[Steamworks] Error setting item vote:', error);
1268
+ return false;
1269
+ }
1270
+ }
1271
+ /**
1272
+ * Gets the user's vote on a Workshop item
1273
+ *
1274
+ * @param publishedFileId - Workshop item ID
1275
+ * @returns Vote status object, or null if failed
1276
+ *
1277
+ * @example
1278
+ * ```typescript
1279
+ * const itemId = BigInt('123456789');
1280
+ * const vote = await steam.workshop.getUserItemVote(itemId);
1281
+ *
1282
+ * if (vote) {
1283
+ * if (vote.votedUp) {
1284
+ * console.log('You voted up');
1285
+ * } else if (vote.votedDown) {
1286
+ * console.log('You voted down');
1287
+ * } else {
1288
+ * console.log('You have not voted');
1289
+ * }
1290
+ * }
1291
+ * ```
1292
+ */
1293
+ async getUserItemVote(publishedFileId) {
1294
+ if (!this.apiCore.isInitialized()) {
1295
+ console.error('[Steamworks] Steam API not initialized');
1296
+ return null;
1297
+ }
1298
+ const ugc = this.apiCore.getUGCInterface();
1299
+ if (!ugc) {
1300
+ console.error('[Steamworks] ISteamUGC interface not available');
1301
+ return null;
1302
+ }
1303
+ try {
1304
+ const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_GetUserItemVote(ugc, publishedFileId);
1305
+ if (callHandle === BigInt(0)) {
1306
+ console.error('[Steamworks] Failed to initiate get vote');
1307
+ return null;
1308
+ }
1309
+ // Wait for the callback result
1310
+ const result = await this.callbackPoller.poll(BigInt(callHandle), GetUserItemVoteResult_t, callbackTypes_1.K_I_GET_USER_ITEM_VOTE_RESULT, 50, // max retries (5 seconds total)
1311
+ 100 // delay ms
1312
+ );
1313
+ if (result && result.m_eResult === 1) { // k_EResultOK = 1
1314
+ return {
1315
+ votedUp: result.m_bVotedUp,
1316
+ votedDown: result.m_bVotedDown,
1317
+ voteSkipped: result.m_bVoteSkipped
1318
+ };
1319
+ }
1320
+ console.error(`[Steamworks] Get user item vote failed with result: ${result?.m_eResult || 'unknown'}`);
1321
+ return null;
1322
+ }
1323
+ catch (error) {
1324
+ console.error('[Steamworks] Error getting item vote:', error);
1325
+ return null;
1326
+ }
1327
+ }
1328
+ /**
1329
+ * Adds a Workshop item to the user's favorites
1330
+ *
1331
+ * @param appId - App ID
1332
+ * @param publishedFileId - Workshop item ID
1333
+ * @returns True if added successfully, false otherwise
1334
+ *
1335
+ * @example
1336
+ * ```typescript
1337
+ * const itemId = BigInt('123456789');
1338
+ * const success = await steam.workshop.addItemToFavorites(480, itemId);
1339
+ *
1340
+ * if (success) {
1341
+ * console.log('Added to favorites!');
1342
+ * }
1343
+ * ```
1344
+ */
1345
+ async addItemToFavorites(appId, publishedFileId) {
1346
+ if (!this.apiCore.isInitialized()) {
1347
+ console.error('[Steamworks] Steam API not initialized');
1348
+ return false;
1349
+ }
1350
+ const ugc = this.apiCore.getUGCInterface();
1351
+ if (!ugc) {
1352
+ console.error('[Steamworks] ISteamUGC interface not available');
1353
+ return false;
1354
+ }
1355
+ try {
1356
+ const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_AddItemToFavorites(ugc, appId, publishedFileId);
1357
+ if (callHandle === BigInt(0)) {
1358
+ console.error('[Steamworks] Failed to initiate add to favorites');
1359
+ return false;
1360
+ }
1361
+ // Wait for the callback result
1362
+ const result = await this.callbackPoller.poll(BigInt(callHandle), UserFavoriteItemsListChanged_t, callbackTypes_1.K_I_USER_FAVORITE_ITEMS_LIST_CHANGED, 50, // max retries (5 seconds total)
1363
+ 100 // delay ms
1364
+ );
1365
+ if (result && result.m_eResult === 1) { // k_EResultOK = 1
1366
+ return true;
1367
+ }
1368
+ console.error(`[Steamworks] Add item to favorites failed with result: ${result?.m_eResult || 'unknown'}`);
1369
+ return false;
1370
+ }
1371
+ catch (error) {
1372
+ console.error('[Steamworks] Error adding item to favorites:', error);
1373
+ return false;
1374
+ }
1375
+ }
1376
+ /**
1377
+ * Removes a Workshop item from the user's favorites
1378
+ *
1379
+ * @param appId - App ID
1380
+ * @param publishedFileId - Workshop item ID
1381
+ * @returns True if removed successfully, false otherwise
1382
+ *
1383
+ * @example
1384
+ * ```typescript
1385
+ * const itemId = BigInt('123456789');
1386
+ * const success = await steam.workshop.removeItemFromFavorites(480, itemId);
1387
+ *
1388
+ * if (success) {
1389
+ * console.log('Removed from favorites!');
1390
+ * }
1391
+ * ```
1392
+ */
1393
+ async removeItemFromFavorites(appId, publishedFileId) {
1394
+ if (!this.apiCore.isInitialized()) {
1395
+ console.error('[Steamworks] Steam API not initialized');
1396
+ return false;
1397
+ }
1398
+ const ugc = this.apiCore.getUGCInterface();
1399
+ if (!ugc) {
1400
+ console.error('[Steamworks] ISteamUGC interface not available');
1401
+ return false;
1402
+ }
1403
+ try {
1404
+ const callHandle = this.libraryLoader.SteamAPI_ISteamUGC_RemoveItemFromFavorites(ugc, appId, publishedFileId);
1405
+ if (callHandle === BigInt(0)) {
1406
+ console.error('[Steamworks] Failed to initiate remove from favorites');
1407
+ return false;
1408
+ }
1409
+ // Wait for the callback result
1410
+ const result = await this.callbackPoller.poll(BigInt(callHandle), UserFavoriteItemsListChanged_t, callbackTypes_1.K_I_USER_FAVORITE_ITEMS_LIST_CHANGED, 50, // max retries (5 seconds total)
1411
+ 100 // delay ms
1412
+ );
1413
+ if (result && result.m_eResult === 1) { // k_EResultOK = 1
1414
+ return true;
1415
+ }
1416
+ console.error(`[Steamworks] Remove item from favorites failed with result: ${result?.m_eResult || 'unknown'}`);
1417
+ return false;
1418
+ }
1419
+ catch (error) {
1420
+ console.error('[Steamworks] Error removing item from favorites:', error);
1421
+ return false;
1422
+ }
1423
+ }
1424
+ }
1425
+ exports.SteamWorkshopManager = SteamWorkshopManager;
1426
+ //# sourceMappingURL=SteamWorkshopManager.js.map