{"id":16682262,"url":"https://github.com/johnfairh/steamworks-swift","last_synced_at":"2026-04-23T18:01:07.647Z","repository":{"id":39910390,"uuid":"418480661","full_name":"johnfairh/steamworks-swift","owner":"johnfairh","description":"Steamworks API in Swift","archived":false,"fork":false,"pushed_at":"2025-11-21T12:59:21.000Z","size":11746,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-16T01:27:02.720Z","etag":null,"topics":["linux","macos","steam","steamworks","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnfairh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-10-18T11:59:49.000Z","updated_at":"2025-12-08T03:03:00.000Z","dependencies_parsed_at":"2022-07-17T14:47:07.641Z","dependency_job_id":"68c2bf5d-5401-4115-8b39-896c04f658b4","html_url":"https://github.com/johnfairh/steamworks-swift","commit_stats":{"total_commits":258,"total_committers":1,"mean_commits":258.0,"dds":0.0,"last_synced_commit":"42953ec68fa7d612bf78ff481a487bb833d73fbd"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/johnfairh/steamworks-swift","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnfairh%2Fsteamworks-swift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnfairh%2Fsteamworks-swift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnfairh%2Fsteamworks-swift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnfairh%2Fsteamworks-swift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnfairh","download_url":"https://codeload.github.com/johnfairh/steamworks-swift/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnfairh%2Fsteamworks-swift/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32191873,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-23T15:28:30.493Z","status":"ssl_error","status_checked_at":"2026-04-23T15:28:29.972Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["linux","macos","steam","steamworks","swift"],"created_at":"2024-10-12T14:06:49.959Z","updated_at":"2026-04-23T18:01:07.637Z","avatar_url":"https://github.com/johnfairh.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![macOS](https://shields.io/badge/platform-macOS%20|%20Linux%20|%20%3F%3F-lightgrey)\n![Steamworks 1.64](https://shields.io/badge/steamworks-1.64-lightgrey)\n[![Test](https://github.com/johnfairh/steamworks-swift/actions/workflows/test.yml/badge.svg)](https://github.com/johnfairh/steamworks-swift/actions/workflows/test.yml)\n![MIT](https://shields.io/badge/license-MIT-black)\n\n# steamworks-swift\n\nA practical interface to the Steamworks SDK using the Swift C++ importer.\n\n**Caveat Integrator: The Swift C++ importer is new and evolving; this package is built on top**\n\nAs of Swift 6, C++ finally seems solid on macOS.  On Linux it's tough to recommend:\nstill tends to break in weird non-portable ways.  I haven't tried Windows.\n\nCurrent state:\n* All Steamworks interfaces complete - see [API docs](https://johnfairh.github.io/steamworks-swift/index.html)\n* Code gen creates Swift versions of Steam types; callbacks and call-returns work\n* Code gen creates SDK header-file oriented type index for documentation\n* Some interface quality-of-life helpers in a separate `SteamworksHelpers` module\n* `make test` builds and runs unit tests that run frame loops and access portions of the Steam API\n  doing various sync and async tasks.\n* Experimental custom-executor for multithreaded Steamworks access in a separate\n  `SteamworksConcurrency` module\n* Encrypted app ticket support in separate `SteamworksEncryptedAppTicket` module\n* Separate demo showing encrypted app-ticket stuff, `make run_ticket`\n* Requires Swift 6.3 / Xcode 26.4\n* The Xcode project basically works.\n* ~Unit tests sometimes crash inside steam on exit~ fixed!\n\nBelow:\n* [Concept](#concept)\n* [API design](#api-mapping-design)\n* [How to use](#how-to-use-this-project)\n* [Implementation notes](#implementation-notes)\n\n## Concept\n\n* Offer a pure Swift module `Steamworks` covering all of the current Steamworks API\n* Leave out the deprecated and WIN32-only stuff\n* Do not diverge too far from the 'real' API names to aid docs / searching / porting:\n  I think this is a better starting point than doing a complete OO analysis to carve\n  out function.  Can go on to augment `SteamworksHelpers` if worthwhile.  Name etc.\n  changes:\n   * _Don't_ use Swift properties for 0-arg getters: diverges too far from Steamworks\n     naming\n   * Drop the intermittent Hungarian notation (argh the 1990s are calling)\n   * Use Swift closures for callbacks as well as async-await sugar\n   * Map unions onto enums with associated values\n* Provide custom API-lifetime and message dispatch classes\n* Provide strongly typed handles\n* Access interfaces via central types\n* Use code gen to deal with the ~900 APIs and their ~400 types, taking advantage of the\n  handy JSON file.  This code-gen piece is the actual main work in this project\n* Provide quality-of-life helpers module `SteamworksHelpers` to wrap up API patterns\n  involving multiple calls, usually determining buffer lengths\n\n### Next\n\n* More SpaceWar porting over to Swift to check general practicality, somewhat real-world usage,\n  general interest - see [spacewar-swift](https://github.com/johnfairh/spacewar-swift).\n\n## API mapping design\n\n### Lifecycle\n\n```swift\n// Initialization\nlet steam = SteamAPI(appID: MyAppId) // or `SteamGameServerAPI`\n\n// Frame loop\nsteam.runCallbacks() // or `steam.releaseCurrentThreadMemory()`\n\n// Shutdown\n// ...when `steam` goes out of scope\n```\n\n### Callbacks\n\nC++\n```cpp\nSTEAM_CALLBACK(MyClass, OnUserStatsReceived, UserStatsReceived_t, m_CallbackUserStatsReceived);\n\n...\n\nm_CallbackUserStatsReceived( this, \u0026MyClass::OnUserStatsReceived )\n\n...\n\nvoid MyClass::OnUserStatsReceived( UserStatsReceived_t *pCallback ) {\n  ...\n}\n```\nSwift\n```swift\nsteam.onUserStatsReceived { userStatsReceived in\n  ...\n}\n```\n\nThere are async versions too, like:\n```swift\nfor await userStatsReceived in steam.userStatsReceived {\n  ...\n}\n```\nBe sure to check [Swift concurrency concerns](#swift-concurrency-concerns).\n\n### Functions\n\n```cpp\nauto handle = SteamInventory()-\u003eStartUpdateProperties();\n```\n```swift\nlet handle = steam.inventory.startUpdateProperties()\n```\n\n### Call-return style\n\nC++\n```cpp\nCCallResult\u003cMyClass, FriendsGetFollowerCount_t\u003e m_GetFollowerCountCallResult;\n\n...\n\nauto hSteamAPICall = SteamFriends.GetFollowerCount(steamID);\nm_GetFollowerCountCallResult.Set(hSteamAPICall, this, \u0026MyClass::OnGetFollowerCount);\n\n...\n\nvoid MyClass::OnGetFollowerCount(FriendsGetFollowerCount_t *pCallback, bool bIOFailure) {\n  ...\n}\n\n```\nSwift\n```swift\nsteam.friends.getFollowerCount(steamID: steamID) { getFollowerCount in\n  guard let getFollowerCount = getFollowerCount else {\n    // `bIOFailure` case\n    ...\n  }\n  ...\n}\n```\n\nThere are async versions:\n```swift\nlet getFollowerCount = await steam.friends.getFollowerCount(steamID: steamID)\n```\n...which are finally safe, as of Swift 6, but do check\n[Swift concurrency concerns](#swift-concurrency-concerns).\n\n### Array-length parameters\n\nParameters carrying the length of an input array are discarded because Swift arrays\ncarry their length with them.\n\n### 'Out' parameters\n\nC++ 'out' parameters filled in by APIs are returned in a tuple, or, if the Steam API\nis `void` then as the sole return value.\n\n```cpp\nSteamInventoryResult_t result;\nbool rc = SteamInventory()-\u003eGrantPromoItems(\u0026result);\n```\n```swift\nlet (rc, result) = steamAPI.inventory.grantPromoItems()\n```\n\n### Optional 'out' parameters\n\nSome C++ 'out' parameters are optional: they can be passed as `NULL` to indicate they're\nnot required by caller.  In the Swift API these generate an additional boolean parameter\n`return\u003cParamName\u003e` with default `true`.\n\n```cpp\nauto avail = SteamNetworkingUtils()-\u003eGetRelayNetworkStatusAvailability(NULL);\n```\n```swift\nlet (avail, _) = steamAPI.networkingUtils.getRelayNetworkStatusAvailability(returnDetails: false)\n```\n\nThe return tuple is still populated with something but its contents is undefined; the\nlibrary guarantees to pass `NULL` to the underlying Steamworks API.\n\n### 'In-out' parameters\n\nC++ parameters whose values are significant and also have their value updated are present\nin _both_ Swift function parameters and the return tuple.\n\n```cpp\nuint32 itemDefIDCount = 0;\nbool rc1 = SteamInventory()-\u003eGetItemDefinitionIDs(NULL, \u0026itemDefIDCount);\nauto itemDefIDs = new SteamItemDef_t [itemDefIDCount];\nbool rc2 = SteamInventory()-\u003eGetItemDefinitions(itemDefIDs, \u0026itemDefIDCount);\n```\n```swift\nlet (rc1, _, itemDefIDCount) = steamAPI.inventory.\n                                   getItemDefinitionIDs(returnItemDefIDs: false,\n                                                        itemDefIDsArraySize: 0)\nlet (rc2, itemDefIDs, _) = steamAPI.inventory.\n                               getItemDefinitionIDs(itemDefIDsArraySize: itemDefIDCount)\n```\n\n### Default parameter values\n\nDefault values are provided where the API docs suggest a value, but there are still APIs\nwhere caller is required to provide a max buffer length for an output string -- these look\npretty weird in Swift but no way to avoid.  Some Steamworks APIs support the old \"pass NULL\nto get the required length\" two-pass style and these patterns are wrapped up in a Swifty\nway in the `SteamworksHelpers` module.\n\n## Swift Concurrency Concerns\n\nThe Steamworks architecture is thread-based.  For each thread you want to call Steam APIs\nyou must regularly call `SteamAPI.runCallbacks()` or `SteamAPI.releaseCurrentThreadMemory()`.\nThe former synchronously calls back into your code to fulfill callbacks; they both do\ninternal thread-specific housekeeping.\n\nSwift concurrency and its built-in libdispatch-based executors are dead set against users\nthinking about threads, with a begrudging exception for 'the main thread'.\n\nTo use async-await with Steamworks I think there are two approaches:\n1. Keep Steam interactions on the main thread.  Use `@MainActor` and related tools to keep\n   your code there (`MainActor.assumeIsolated()` can be a life-saver).  If you need to call\n   Steam from another isolation domain then you have to hop over -- just like with AppKit\n   and friends.\n\n   Call `SteamAPI.runCallbacks()` as part of your frame loop or similar.\n\n2. Use a Swift custom executor to manage a thread to run your code and do the required\n   Steam polling.  Assign instances of these executors to actors to host your program,\n   tastefully choosing the number and distribution of threads.\n\nA couple of examples of (1) in the tests, see `TestApiSimple.testCallReturnAsync()` and\n`TestApiSimple.testCallbackAsync()` along with their callback-based versions.\n\nA prototype executor for (2) in ``SteamExecutor`` in the `SteamworksConcurrency` module,\nalong with an example of use in `TestExecutor.testExecutorSteam()`.\n\nI think a practical solution is to mix these: use `@MainActor`-bound code for general\nthings, using the frame loop to trigger frequent callbacks, and then use one or more\nexecutors to look after gameservers or lower-priority work.\n\n## How To Use This Project\n\nPrereqs:\n* Needs Swift 6 (Xcode 16+)\n* Needs Steam client installed (and logged-in, running for the tests or to do anything useful)\n* I'm using macOS 15; should work on Linux; might work on Windows eventually\n\nInstall the Steamworks SDK:\n* Clone [steamworks-swift-sdk](https://github.com/johnfairh/steamworks-swift-sdk)\n* `make install`\n(this is far from ideal but hard stuck behind various Swift issues)\n\nSample `Package.swift`:\n```swift\n// swift-tools-version: 6.0\n\nimport PackageDescription\n\nlet package = Package(\n  name: \"MySteamApp\",\n  platforms: [\n    .macOS(\"15.0\"),\n  ],\n  dependencies: [\n    .package(url: \"https://github.com/johnfairh/steamworks-swift\", from: \"1.3.0\"),\n  ],\n  targets: [\n    .executableTarget(\n      name: \"MySteamApp\",\n      dependencies: [\n        .product(name: \"Steamworks\", package: \"steamworks-swift\")\n      ],\n      swiftSettings: [.interoperabilityMode(.Cxx)]\n    )\n  ]\n)\n```\n\nNote that you must set `.interoperabilityMode(.Cxx)` in all targets that depend on\nSteamworks, _and_ all targets that depend on them, forever and forever unto the last\ndependency.  This virality is part of the current Swift design and unavoidable for now.\n\nSample skeleton program:\n```swift\nimport Steamworks\n\n@main\npublic struct MySteamApp {\n  public static func main() {\n    guard let steam = SteamAPI(appID: .spaceWar, fakeAppIdTxtFile: true) else {\n      print(\"SteamInit failed\")\n      return\n    }\n    print(\"Hello world with Steam name \\(steam.friends.getPersonaName())\")\n  }\n}\n```\n\nAPI docs [here](https://johnfairh.github.io/steamworks-swift/index.html).\n\nFully-fledged AppKit/Metal demo [here](https://github.com/johnfairh/spacewar-swift).\n\n## Implementation notes\n\n### Swift C++ Bugs\n\nMostly fixed in Swift 6.  Linux still suffering a bit.\n\nTech limitations, on 6.0 Xcode 16.b3:\n* Some structures/classes aren't imported -- is the common factor a `protected`\n  destructor?  Verify by trying to use `SteamNetworkingMessage_t`.\n* ~Something goes wrong storing pointers to classes and they get nobbled by something.\n  Verify by making `SteamIPAddress` a struct and running `TestApiServer`.  Or change\n  interfaces to cache the interface pointers.~ incredibly, fixed in Swift 6\n* ~Calls to virtual functions aren't generated properly: Swift generates a ref\n  to a symbol instead of doing the vtable call.  So the actual C++ interfaces are not\n  usable in practice.  Will use the flat API.~ allegedly fixed in Swift 6 but don't\n  need due to history.\n* Anonymous enums are not imported at all.  Affects callback etc. ID constants.\n  Will work around.\n* ~sourcekit won't give me a module interface for `CSteamworks` to see what else the\n  importer is doing.  Probably Xcode's fault, still not passing the user's flags to\n  sourcekit and still doing insultingly bad error-reporting.~ fixed in Xcode 15?!\n* ~Linux only: random parts of Glibc silently fail to import. SMH.  Work around in C++.\n  See `swift_shims.h`.~ Fixed in 6.0 (\"for now\")\n* ~Linux only: implicit struct constructors are not created, Swift generates a ref\n  to a non-existent method that fails at link time.  Work around with dumb C++\n  allocate shim.~  ~Sort of fixed in 5.9, but instead `swiftc` crashes on some uses -- on\n  both macOS and Linux.  Check by refs to eg. `CSteamNetworkingIPAddr_Allocate()`, see\n  `steam_missing.h`.~ Fixed in 6.0.\n* Linux only, _again_: SPM test auto-discovery has no clue about C++ interop.  Work around by\n  smashing in the flag everywhere... Swift 6 - worse now, utterly broken on Linux with\n  yams 3rd-party dependency.  Maybe fixable with swift-testing - for now nobbled those tests\n  on Linux.  Yay?\n* ~Swift 5.8+ adopts a broken/paranoid model about 'projected pointers' requiring some fairly\n  ugly code to work around.   Verify with the `__ unsafe` stuff in `ManualTypes.swift`.~\n  fixed by Swift 6ish\n\n### Non-Swift Problems\n\n* Some [Steamworks SDK issues](#json-notes), nothing too serious.\n* CI really needs a private runner with a logged-in steam account, current version\n  just runs the non-steam-requiring tests.\n\n### Weird Steam messages\n\nGetting unexpected SteamAPICallCompleteds out of\n`SteamAPI_ManualDispatch_GetNextCallback()` -- suspect parts of steamworks trying to\nuse callbacks internally without understanding manual dispatch mode.  Or I'm missing\nan API somewhere to dispatch them.\n\n* 2101 - `HTTPRequestCompleted_t.k_iCallback`\n* 1296 - `k_iSteamNetworkingUtilsCallbacks + 16` - undefined, not a clue\n\nSeems triggered by using steamnetworking.\n\nFacepunch logs \u0026 drops these too, so, erm, shrug I suppose.\n\nGetting `src/steamnetworkingsockets/clientlib/csteamnetworkingmessages.cpp (229) : Assertion Failed: [#40725897 pipe] Unlinking connection in state 1` using steamnetworkingmessages; possibly\nit's not expecting to send messages from a steam ID to itself.\n\n### JSON notes\n\nCapture some notes on troubles reflecting the json into the module.\n\n* The 'modern' `isteamnetworking` stuff is incomplete somehow - Json describes\n  `SteamDatagramGameCoordinatorServerLogin`, `SteamDatagramHostedAddress` are missing\n  from the header files.  The online API docs are hilariously broken here, scads of\n  broken links.  Have to wait for Valve to fix this.\n\n  I found some of this in the SDR SDK, but it's not supported on macOS and uses actual\n  grown-up C++ with `std::string` and friends so best leave it alone for now.\n\n* `SteamNetworkingMessage_t` doesn't import into Swift.  Probably stumbling into a hole\n  of C++ struct with function pointer fields.  Trust Apple will get to this eventually,\n  will write a zero-cost inline shim.\n\n* Json (and all non-C languages) struggles with unions.  Thankfully rare:\n  `SteamIPAddress_t`, `SteamInputAction_t`, `SteamNetworkingConfigValue_t`.\n  `SteamNetworkingConfigValue_t`.  Rare enough to deal with manually.\n\n* Loads of missing `out_string_count` etc. annotations and a few wrong, see patchfile.\n\n## Contributions\n\nWelcome: open an issue / johnfairh@gmail.com / @johnfairh@mastodon.social\n\n## License\n\nDistributed under the MIT license.  Except the Steamworks SDK parts.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnfairh%2Fsteamworks-swift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnfairh%2Fsteamworks-swift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnfairh%2Fsteamworks-swift/lists"}