-
Notifications
You must be signed in to change notification settings - Fork 0
Regenerating
src/generated/ is written by scripts/generate.ts from steam_api.json,
Valve's machine-readable description of the flat C API. The generated code is
committed. The SDK it came from is not.
Valve's license does not allow redistributing the headers or steam_api.json,
so .gitignore blocks both the unpacked SDK and the archive:
sdk/
steamworks_sdk_*.zip
Shipping the redistributable binaries is allowed and normal practice, so
runtime/win64/steam_api64.dll, runtime/linux64/libsteam_api.so and
runtime/osx/libsteam_api.dylib are committed and published in the package.
sdk.lock.json records which SDK the committed output came from:
{
"sdkVersion": "1.65",
"steamApiJsonSha256": "8c1038d41b236571c69dfdeb3f26171a7ff3659372d897ecec7f9d66d8e17d20",
"sdkZipSha256": "8c42792e09100988e31e3dc069de2eb1bc60702a0445bb37298ba0c54067c202"
}- Download the Steamworks SDK from partner.steamgames.com/downloads/list. A Steamworks partner account is required.
- Unpack the archive so that
steam_api.jsonsits atsdk/public/steam/steam_api.json.
Keep the SDK somewhere else if you prefer, and point the generator at it:
export STEAMWORKS_SDK=/path/to/steamworks_sdkWithout that variable the generator reads ./sdk. If the file is missing it
prints steam_api.json not found at <path>. Set STEAMWORKS_SDK or place the SDK in ./sdk. and exits with code 1.
pnpm generateIt deletes src/generated/ and rewrites it: enums.ts, consts.ts,
structs.ts, callbacks.ts, index.ts, and one file per interface under
interfaces/. Every file starts with
// GENERATED by scripts/generate.ts from steam_api.json (SDK 1.65) - do not edit.
If the SHA-256 of steam_api.json does not match sdk.lock.json, it warns
before generating and continues:
WARNING: steam_api.json hash differs from sdk.lock.json (SDK 1.65).
lock: 8c1038...
actual: ...
Update sdk.lock.json if this SDK bump is intentional, then review the generated diff.
The run ends with a report. On SDK 1.65 it reads:
generated: 25 interfaces, 215 struct layouts, 191 callbacks
structs without layouts (10):
...
methods skipped (18):
...
Read all three parts. A growing skip list or a new layout failure is the signal that the SDK changed something the generator does not handle.
-
Unpack the new SDK and update
sdk.lock.json:sdkVersion, the SHA-256 ofpublic/steam/steam_api.json, and the SHA-256 of the downloaded archive. -
Run
pnpm generate. -
Read the report. Compare the "structs without layouts" and "methods skipped" lists against the previous run. New entries mean lost surface.
-
Run the offset regression tests:
pnpm testtest/offsets.test.tspins the workshop set (CreateItemResult_t,SubmitItemUpdateResult_t,SteamUGCQueryCompleted_t,SteamUGCDetails_t,ItemInstalled_t,DownloadItemResult_t) with both the win64 and posix offsets, plus the five workshop callback ids. A moved offset fails the test. -
If an offset moved, do not just update the expectation. Verify the new value against an independent source first. The current numbers were checked against steamworks-sys 0.13.0's bindgen layout asserts: 428 comparisons, no differences. Then edit the test.
-
Check the interface accessor versions in the diff. A bump from
SteamAPI_SteamUGC_v021tov022is the whole point of regenerating, but it also means the bundled redistributables inruntime/must be replaced with the ones from the same SDK. -
Finish with:
pnpm typecheck
pnpm build
-
If a Steam client is available,
pnpm smokeandpnpm test:liveconfirm the new binding still talks to Steam. See Development.
Callback structs are packed at 8 bytes on Windows
(VALVE_CALLBACK_PACK_LARGE) and 4 bytes on Linux and macOS
(VALVE_CALLBACK_PACK_SMALL). Every struct is laid out twice, once per pack,
and both tables are emitted:
structLayouts.CreateItemResult_t // { win64: {...}, posix: {...} }layoutOf(name) picks by process.platform. The layout walk is the standard
C rule with the pack clamp: each field's effective alignment is
Math.min(fieldAlign, pack), the offset rounds up to that, and the struct
size rounds up to the largest effective alignment.
Field types resolve like this:
-
char[N]becomes{ cstring: N }and decodes to a NUL-terminated string. - Any other array becomes
{ bytes: stride * N }and decodes to aBuffer. - Pointers and references become
uint64, size 8, align 8. -
CSteamIDandCGameIDare size 8 with align 1, becausesteamclientpublic.hdeclares them under#pragma pack(push, 1). Treating them as ordinaryuint64would shift every following field. - Enums resolve to
int32. - A nested struct is laid out recursively and embedded as
{ bytes: size }. Recursion through the same struct name is reported as a failure, not followed.
FORCED_PACK overrides the pack for six Steam Input structs that the headers
declare under their own #pragma pack(1): InputAnalogActionData_t,
InputDigitalActionData_t, InputMotionData_t, and the three
Controller*Data_t equivalents.
UNION_STRUCTS lists four structs that contain a C union:
SteamNetworkingIdentity, SteamNetworkingIPAddr,
SteamNetworkingMessage_t, SteamInputActionEvent_t. steam_api.json has no
way to express a union, so a layout computed from the field list would be
silently wrong. They are excluded, and so is every struct that embeds one.
That is 10 structs on SDK 1.65, all listed in the report. At runtime,
layoutOf on one of them throws
steamwand: no generated layout for struct <name>.
callbacksById and callbackId are emitted from callback_structs, skipping
any struct with no layout, and skipping the second definition of a duplicated
callback id. Some game server structs alias a user callback id (id 1108 is
both UserStatsUnloaded_t and GSStatsUnloaded_t); the first, user-side
definition wins. 196 callback structs in SDK 1.65 produce 191 entries.
consts.ts evaluates each constval expression itself. It drops C casts,
rewrites 600.f to 600, strips ull/ll/ul/u/f literal suffixes,
substitutes constants defined earlier in the file, then checks the result
against a numeric-only pattern before evaluating it. 64-bit types are
evaluated as BigInt and masked to 64 bits for unsigned types; 32-bit
integers get C semantics (| 0, then >>> 0 for uint32).
Anything that does not reduce to a number is skipped, logged as
consts skipped (unparseable values):, and listed in a trailing comment in
the generated file. String constants and expressions that reference C macros
land here.
One class per interface, named after classname, with the versioned accessor
called in the constructor. An interface is emitted only if it has an accessor
of kind user or global. That is why 34 interfaces in steam_api.json
become 25 classes: the game server interfaces have neither.
Methods are emitted with the flat name minus the SteamAPI_<Class>_ prefix,
so SteamAPI_ISteamUGC_SetItemTitle becomes SetItemTitle. Parameters map
as:
-
const char *to'str'(a JSstring). -
SteamParamStringArray_t *to the koffi struct pointer, taken asstringArray(['a', 'b']). -
HServerListRequest, the one opaque handle missing from the SDK's typedef list, to'void *'. - Any other pointer or reference to
'void *', taken asBuffer | null. - Everything else through the scalar resolver.
A parameter or return type the scalar resolver cannot handle skips the whole
method, recorded as SteamAPI_X_Y (param <type>) or
SteamAPI_X_Y (return <type>).
- 18 methods are skipped on SDK 1.65: the ones taking a C function pointer (debug hooks, netsockets status callbacks) and the ones passing a struct by value (Steam Input action data). The exact list is in the generator report.
- 10 structs get no layout, because of unions. See "Excluded structs".
-
Game server interfaces are not generated at all, and are not wired into
Steam. -
Reserved JS words used as parameter names get a trailing
_(functionbecomesfunction_), coveringfunction,default,delete,new,var,class,in, andthis.
Everything the generator emits is described in How It Works, and the raw layer it produces is documented in Flat API.