stovesdk.js 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Unofficial Node.js / Electron bindings for the **STOVE PC SDK** (N-API).
4
4
 
5
5
  The STOVE PC SDK ships official bindings for C, C#, and C++ only. This project wraps the native SDK so Electron and Node.js games can use it the way `steamworks.js` wraps Steamworks.
6
6
 
7
- > **Status: alpha.** Install from npm as `stovesdk.js`; the Windows x64 prebuild ships inside the package and is also attached to each [GitHub release](https://github.com/firejune/stovesdk.js/releases). See *Install* and *Build* below.
7
+ > **Status: alpha.** Install from npm as `stovesdk.js`; the Windows x64 prebuild ships inside the package and is also attached to each [GitHub release](https://github.com/Blosharper/stovesdk.js/releases). See *Install* and *Build* below.
8
8
 
9
9
  ## Scope
10
10
 
@@ -26,10 +26,10 @@ Windows x64 only, which matches the STOVE PC SDK itself. The addon targets N-API
26
26
 
27
27
  The STOVE PC SDK is **not** included in this repository and is not redistributed by this package.
28
28
 
29
- 1. Download the STOVE PC SDK from the STOVE developer portal under your own developer account.
29
+ 1. Download the STOVE PC SDK from the STOVE developer portal under your own developer account — or run `node tools/fetch-sdk.js sdk`, which fetches the drop this module is built against (version, URLs and SHA-256 of each archive are pinned in [`tools/sdk-manifest.json`](tools/sdk-manifest.json); a checksum mismatch fails the fetch).
30
30
  2. Place it under [`sdk/`](sdk/README.md), or point `STOVE_PCSDK_DIR` at your copy. The expected layout is documented in `sdk/README.md`; `npm run check-sdk` tells you what is missing.
31
31
 
32
- Your use of the SDK is governed by STOVE's own terms.
32
+ Your use of the SDK is governed by STOVE's own terms — the developer agreement of your STOVE Studio account. That the archives download without a credential does not make them redistributable, and this package does not redistribute them.
33
33
 
34
34
  ## Install
35
35
 
@@ -57,10 +57,10 @@ An Electron app usually ships the `.node` in its own resources, next to the thre
57
57
  ### Installing from a git tag instead
58
58
 
59
59
  ```sh
60
- npm install github:firejune/stovesdk.js#vX.Y.Z
60
+ npm install github:Blosharper/stovesdk.js#vX.Y.Z
61
61
  ```
62
62
 
63
- installs the same package without the prebuild (binaries are not tracked in git). Download `stovesdk.node` and `SHA256SUMS` from the [GitHub release](https://github.com/firejune/stovesdk.js/releases) of the same version, check the sum (`sha256sum -c SHA256SUMS`, or `CertUtil -hashfile stovesdk.node SHA256`), and put the binary in one of the places above. (v0.1.0 still runs `node-gyp rebuild` on install; add `--ignore-scripts` for that one.)
63
+ installs the same package without the prebuild (binaries are not tracked in git). Download `stovesdk.node` and `SHA256SUMS` from the [GitHub release](https://github.com/Blosharper/stovesdk.js/releases) of the same version, check the sum (`sha256sum -c SHA256SUMS`, or `CertUtil -hashfile stovesdk.node SHA256`), and put the binary in one of the places above. (v0.1.0 still runs `node-gyp rebuild` on install; add `--ignore-scripts` for that one.)
64
64
 
65
65
  Or build the binary yourself, below.
66
66
 
@@ -160,6 +160,24 @@ Notes:
160
160
  - A stat that has never been written reports `currentValue` as `-2147483648`. That is the SDK's own `int32` value, passed through unchanged.
161
161
  - Leave a moment between `uninitialize()` and the next `initialize()`. The STOVE client re-creates its side of the connection after a disconnect, and an immediate re-initialize can answer `{ status: 'restart' }`.
162
162
 
163
+ ### Coverage — what is wrapped and what is not
164
+
165
+ The SDK drop has seven modules. This module links three and wraps the calls a shipping game has needed so far; the rest is listed so you know before you start, not after. Coverage grows by consumer request (ROADMAP), not by the size of the SDK — open an issue naming the SDK call and what the game needs it for.
166
+
167
+ | SDK module | Wrapped | Not wrapped (present in the SDK) |
168
+ | --- | --- | --- |
169
+ | **BaseSDK** | initialize / uninitialize (`Base_RestartAppIfNecessaryAsync`, `Base_Initialize`, `Base_UnInitialize`), the callback pump (`Base_RunCallback`), login identity (`Base_GetUser`, `Base_GetGds`), `Base_GetVersion` | access tokens for a game server (`Base_GetAccessToken`, `Base_GetRenewToken`, `Base_AccessTokenRenewed`); `Base_SetLanguage`, `Base_SetGameProfile`; the launcher's shutdown request (`Base_GetShutdown`, `Base_ShutdownNotification`); over-immersion notices (`Base_GetOverImmersion`, `Base_OverImmersionNotification`); `Base_GetSignin`, `Base_GetTraceHint`; `Base_RunCallbackWithTimeout`; the synchronous `Base_RestartAppIfNecessary` |
170
+ | **OwnershipSDK** | everything: `Ownership_OwnershipList` (with `Ownership_Initialize` / `Ownership_UnInitialize` managed for you) | — |
171
+ | **GameSupportSDK** | achievements and stats (`GameSupport_Achievement`, `GameSupport_AllAchievement`, `GameSupport_Stat`, `GameSupport_ModifyStat`) | leaderboards (`GameSupport_Rank`) |
172
+ | **ViewSDK** | — | the whole module: overlay and in-game popups |
173
+ | **IAPSDK** | — | the whole module: in-game purchases |
174
+ | **GamingServicesSDK** | — | the whole module |
175
+ | **PCBangSDK** | — | the whole module (PC-café entitlements) |
176
+
177
+ Not provided by the SDK at all, so not something this module can add: an achievement "unlock" call (achievements complete through their stat — see *Notes*), an achievement reset, cloud-save queries (cloud save is configured in STOVE Studio, not in code), controller input, and anything Steam-specific such as rich presence or workshop.
178
+
179
+ Also out of scope by design, whatever the SDK offers: decisions on the caller's behalf — what to do when ownership fails, when to quit, what to show. The module reports; the game decides.
180
+
163
181
  ### Error codes
164
182
 
165
183
  Rejected Promises carry `step`, `sdk`, `method`, `code` and `externalError`. `code` is the SDK's own result code (unsigned) when the SDK answered, or one of these negative addon codes when the addon refused the call:
@@ -189,7 +207,7 @@ SDK callbacks are plain C function pointers with no user-data slot, and the vend
189
207
 
190
208
  ## Prebuilds, publishing and CI
191
209
 
192
- [`.github/workflows/prebuild.yml`](.github/workflows/prebuild.yml) is a manually triggered Windows x64 build that expects the SDK to be present on the runner at `STOVE_PCSDK_DIR`. Because the SDK is not public, the workflow does not download it; how CI gets the SDK is an open design question — see the workflow header for the options under consideration. Until it is settled, the prebuild is built by the maintainer and attached to the GitHub release.
210
+ [`.github/workflows/prebuild.yml`](.github/workflows/prebuild.yml) is dispatched by hand for a release tag. On a GitHub-hosted Windows x64 runner it fetches the pinned SDK drop from the vendor's download CDN (verifying each archive's checksum first), builds the tagged commit, runs the load-only smoke and attaches `stovesdk.node` and `SHA256SUMS` to the GitHub release — nothing from the SDK leaves the runner. The smoke involves no STOVE client; it proves the addon links and loads, not that it talks to STOVE.
193
211
 
194
212
  [`.github/workflows/publish.yml`](.github/workflows/publish.yml) publishes a tagged release to npm, by manual dispatch once its prebuild is attached: it takes `stovesdk.node` from the GitHub release, checks it against `SHA256SUMS`, checks that the tarball carries that one binary and nothing from the SDK, and publishes over OIDC trusted publishing with provenance. [RELEASING.md](RELEASING.md) has the sequence.
195
213
 
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "stovesdk.js",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Unofficial Node.js / Electron bindings for the STOVE PC SDK (N-API)",
5
5
  "license": "MIT",
6
6
  "author": "firejune",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/firejune/stovesdk.js.git"
9
+ "url": "git+https://github.com/Blosharper/stovesdk.js.git"
10
10
  },
11
+ "bugs": {
12
+ "url": "https://github.com/Blosharper/stovesdk.js/issues"
13
+ },
14
+ "homepage": "https://github.com/Blosharper/stovesdk.js#readme",
11
15
  "keywords": [
12
16
  "stove",
13
17
  "pcsdk",
@@ -1 +1 @@
1
- f9ac8af314ddee58b85b57836ac7703acae28184691ce5401757aff36b906210 *./stovesdk.node
1
+ 799924d339bf570c0fbddc3a00d4a5289216af8f80b85aa91fed0ecbc3ddd77b *./stovesdk.node
Binary file
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ // Fetches the pinned STOVE PC SDK drop from the vendor's download CDN into a directory laid out
5
+ // the way lib/sdk-dir.js expects, verifying every archive against tools/sdk-manifest.json first.
6
+ //
7
+ // node tools/fetch-sdk.js <dest> fetch, verify, extract into <dest>, validate the layout
8
+ // node tools/fetch-sdk.js <dest> --check only validate an existing <dest> against the manifest
9
+ //
10
+ // What this encodes (issue #21):
11
+ // - The SDK version, the archive URLs and their SHA-256 are pinned in the manifest and reviewed
12
+ // like code. Nothing here derives a URL or accepts an unlisted module.
13
+ // - Every archive is hashed before anything is extracted. A mismatch fails the whole fetch: no
14
+ // other version is tried, no hash is rewritten, nothing from that archive touches <dest>.
15
+ // - <dest> must be empty or absent, so two drops never mix; archives are downloaded under it and
16
+ // deleted after extraction, so nothing is cached between runs.
17
+ // - The vendor's zips unpack to `Deploy/…` with no module folder, so each one is extracted into
18
+ // <dest>/<Module>/; that is the layout sdk/README.md documents and binding.gyp reads.
19
+ // - The extracted files are the vendor's. This tool puts them in a build directory and nowhere
20
+ // else — never into the repository, a package or a release.
21
+
22
+ const fs = require('fs')
23
+ const path = require('path')
24
+ const crypto = require('crypto')
25
+ const { execFileSync } = require('child_process')
26
+ const { SDK_MODULES, missingSdkFiles } = require('../lib/sdk-dir')
27
+
28
+ const MANIFEST_FILE = path.join(__dirname, 'sdk-manifest.json')
29
+
30
+ // Problems with a manifest object (empty = usable). Checked before any network access.
31
+ function checkManifest(manifest) {
32
+ const problems = []
33
+ if (!manifest || typeof manifest !== 'object') return ['manifest is not an object']
34
+ if (typeof manifest.version !== 'string' || !/^\d+(\.\d+)+$/.test(manifest.version)) problems.push('version must be a dotted version string')
35
+ const modules = manifest.modules && typeof manifest.modules === 'object' ? manifest.modules : null
36
+ if (!modules) return [...problems, 'modules must be an object']
37
+ const names = Object.keys(modules)
38
+ for (const mod of SDK_MODULES) if (!names.includes(mod)) problems.push(`modules.${mod} is missing`)
39
+ for (const name of names) {
40
+ if (!SDK_MODULES.includes(name)) problems.push(`modules.${name} is not an SDK module this addon links (${SDK_MODULES.join(', ')})`)
41
+ const entry = modules[name]
42
+ if (!entry || typeof entry !== 'object') { problems.push(`modules.${name} must be an object`); continue }
43
+ if (typeof entry.url !== 'string' || !/^https:\/\/[^\s]+\.zip$/.test(entry.url)) problems.push(`modules.${name}.url must be an https URL to a .zip`)
44
+ if (typeof entry.sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(entry.sha256)) problems.push(`modules.${name}.sha256 must be 64 lowercase hex characters`)
45
+ }
46
+ return problems
47
+ }
48
+
49
+ function loadManifest(file = MANIFEST_FILE) {
50
+ const manifest = JSON.parse(fs.readFileSync(file, 'utf8'))
51
+ const problems = checkManifest(manifest)
52
+ if (problems.length) {
53
+ const err = new Error(`[stovesdk] ${path.basename(file)} is not usable:\n${problems.map(p => ` - ${p}`).join('\n')}`)
54
+ err.code = 'STOVE_PCSDK_SDK_MANIFEST'
55
+ throw err
56
+ }
57
+ return manifest
58
+ }
59
+
60
+ function sha256Hex(buffer) {
61
+ return crypto.createHash('sha256').update(buffer).digest('hex')
62
+ }
63
+
64
+ // Throws (code STOVE_PCSDK_SDK_CHECKSUM_MISMATCH) unless the archive bytes hash to the pinned value.
65
+ function verifyArchive(buffer, module, expected, url) {
66
+ const actual = sha256Hex(buffer)
67
+ if (actual === expected) return actual
68
+ const err = new Error(
69
+ `[stovesdk] ${module}: SHA-256 mismatch for ${url}\n expected ${expected}\n actual ${actual}\n` +
70
+ 'The archive is not the pinned drop. Nothing was extracted. If the vendor re-published this version, ' +
71
+ 'review the change and update tools/sdk-manifest.json in a pull request of its own.'
72
+ )
73
+ err.code = 'STOVE_PCSDK_SDK_CHECKSUM_MISMATCH'
74
+ err.module = module
75
+ err.expected = expected
76
+ err.actual = actual
77
+ throw err
78
+ }
79
+
80
+ async function downloadBuffer(url) {
81
+ const res = await fetch(url, { redirect: 'follow' })
82
+ if (!res.ok) {
83
+ const err = new Error(`[stovesdk] download failed: HTTP ${res.status} for ${url}`)
84
+ err.code = 'STOVE_PCSDK_SDK_DOWNLOAD'
85
+ throw err
86
+ }
87
+ return Buffer.from(await res.arrayBuffer())
88
+ }
89
+
90
+ // bsdtar reads zip archives. On Windows it is System32\tar.exe (Windows 10 1803+), named in full
91
+ // because a Git Bash PATH puts GNU tar — which does not read zip — in front of it. Elsewhere `tar`
92
+ // is bsdtar on macOS; on Linux, where GNU tar is the default, `unzip` is tried next.
93
+ function extractZip(zipFile, dir) {
94
+ fs.mkdirSync(dir, { recursive: true })
95
+ if (process.platform === 'win32') {
96
+ const tar = path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe')
97
+ execFileSync(tar, ['-xf', zipFile, '-C', dir], { stdio: 'inherit' })
98
+ return
99
+ }
100
+ try {
101
+ execFileSync('tar', ['-xf', zipFile, '-C', dir], { stdio: ['ignore', 'inherit', 'pipe'] })
102
+ } catch (tarErr) {
103
+ try {
104
+ execFileSync('unzip', ['-q', '-o', zipFile, '-d', dir], { stdio: 'inherit' })
105
+ } catch {
106
+ const err = new Error(`[stovesdk] could not extract ${zipFile}: need bsdtar or unzip (tar said: ${String(tarErr.stderr || tarErr.message).trim()})`)
107
+ err.code = 'STOVE_PCSDK_SDK_EXTRACT'
108
+ throw err
109
+ }
110
+ }
111
+ }
112
+
113
+ function isEmptyDir(dir) {
114
+ if (!fs.existsSync(dir)) return true
115
+ return fs.statSync(dir).isDirectory() && fs.readdirSync(dir).length === 0
116
+ }
117
+
118
+ function layoutError(dest, missing) {
119
+ const err = new Error(`[stovesdk] SDK layout incomplete at ${dest}. Missing:\n${missing.map(f => ` - ${f}`).join('\n')}`)
120
+ err.code = 'STOVE_PCSDK_SDK_LAYOUT'
121
+ err.missing = missing
122
+ return err
123
+ }
124
+
125
+ // Fetches, verifies and extracts every module of the manifest into `dest`. Resolves with a summary.
126
+ // `download` and `extract` are injectable so the rules above can be tested without the network.
127
+ async function fetchSdk(dest, { manifest = loadManifest(), download = downloadBuffer, extract = extractZip, log = () => {} } = {}) {
128
+ const problems = checkManifest(manifest)
129
+ if (problems.length) {
130
+ const err = new Error(`[stovesdk] manifest is not usable:\n${problems.map(p => ` - ${p}`).join('\n')}`)
131
+ err.code = 'STOVE_PCSDK_SDK_MANIFEST'
132
+ throw err
133
+ }
134
+ dest = path.resolve(dest)
135
+ if (!isEmptyDir(dest)) {
136
+ const err = new Error(`[stovesdk] ${dest} exists and is not empty. The fetch only fills an empty directory, so two drops never mix.`)
137
+ err.code = 'STOVE_PCSDK_SDK_DEST_NOT_EMPTY'
138
+ throw err
139
+ }
140
+ fs.mkdirSync(dest, { recursive: true })
141
+ const downloads = path.join(dest, '.download')
142
+ fs.mkdirSync(downloads)
143
+ const modules = []
144
+ try {
145
+ for (const module of SDK_MODULES) {
146
+ const { url, sha256 } = manifest.modules[module]
147
+ log(`[stovesdk] ${module}: downloading ${url}`)
148
+ const buffer = await download(url)
149
+ verifyArchive(buffer, module, sha256, url)
150
+ log(`[stovesdk] ${module}: ${buffer.length} bytes, SHA-256 ${sha256} OK`)
151
+ const zipFile = path.join(downloads, `${module}.zip`)
152
+ fs.writeFileSync(zipFile, buffer)
153
+ extract(zipFile, path.join(dest, module))
154
+ fs.rmSync(zipFile, { force: true })
155
+ modules.push({ module, url, sha256, bytes: buffer.length })
156
+ }
157
+ } finally {
158
+ fs.rmSync(downloads, { recursive: true, force: true })
159
+ }
160
+ fs.writeFileSync(path.join(dest, 'VERSION'), `${manifest.version}\n`)
161
+ const missing = missingSdkFiles(dest)
162
+ if (missing.length) throw layoutError(dest, missing)
163
+ return { dest, version: manifest.version, modules }
164
+ }
165
+
166
+ module.exports = { MANIFEST_FILE, checkManifest, loadManifest, sha256Hex, verifyArchive, fetchSdk }
167
+
168
+ if (require.main === module) {
169
+ const args = process.argv.slice(2)
170
+ const check = args.includes('--check')
171
+ const dest = args.find(a => !a.startsWith('--'))
172
+ if (!dest) {
173
+ console.error('usage: node tools/fetch-sdk.js <dest> [--check]')
174
+ process.exit(2)
175
+ }
176
+ if (check) {
177
+ const manifest = loadManifest()
178
+ const missing = missingSdkFiles(path.resolve(dest))
179
+ if (missing.length) { console.error(layoutError(path.resolve(dest), missing).message); process.exit(1) }
180
+ const version = fs.existsSync(path.join(dest, 'VERSION')) ? fs.readFileSync(path.join(dest, 'VERSION'), 'utf8').trim() : 'unknown'
181
+ if (version !== manifest.version) { console.error(`[stovesdk] ${dest} holds SDK ${version}, the manifest pins ${manifest.version}`); process.exit(1) }
182
+ console.log(`[stovesdk] SDK ${version} complete at ${path.resolve(dest)}`)
183
+ process.exit(0)
184
+ }
185
+ fetchSdk(dest, { log: console.log })
186
+ .then(summary => {
187
+ console.log(`[stovesdk] SDK ${summary.version} ready at ${summary.dest} (${summary.modules.length} modules, ${summary.modules.reduce((n, m) => n + m.bytes, 0)} bytes downloaded, archives removed)`)
188
+ })
189
+ .catch(err => {
190
+ console.error(err && err.message ? err.message : err)
191
+ process.exit(1)
192
+ })
193
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$comment": "The STOVE PC SDK drop that tools/fetch-sdk.js fetches and verifies (issue #21). Reviewed like code: the version is the one the addon has been built and smoke-tested against, and a change here is a pull request of its own, never combined with an addon change. Each archive is checked against its SHA-256 before extraction; a mismatch fails the fetch and nothing is substituted. The files are the vendor's and are never committed, packed or redistributed.",
3
+ "version": "3.3.2.19",
4
+ "source": "STOVE developer-center download CDN (the links behind the PC SDK download page)",
5
+ "modules": {
6
+ "BaseSDK": {
7
+ "url": "https://d2x8kymwjom7h7.cloudfront.net/live/application_no/STATIC/developer-center/Store/BaseSDK_3.3.2.19.zip",
8
+ "sha256": "92e9a0e0a8522c8ef176769695f79aca89de33ae67c27f7c2f41fd26035f5faa"
9
+ },
10
+ "OwnershipSDK": {
11
+ "url": "https://d2x8kymwjom7h7.cloudfront.net/live/application_no/STATIC/developer-center/Store/OwnershipSDK_3.3.2.19.zip",
12
+ "sha256": "b542032b9a4a53a2e7d3fdda7a871aeef4a9ea9222cd149deea60b0c29810409"
13
+ },
14
+ "GameSupportSDK": {
15
+ "url": "https://d2x8kymwjom7h7.cloudfront.net/live/application_no/STATIC/developer-center/Store/GameSupportSDK_3.3.2.19.zip",
16
+ "sha256": "ad3a339fbe28055a88839428333700efa34f3a59590dc3a67583145f437f25e9"
17
+ }
18
+ }
19
+ }
@@ -5,6 +5,12 @@
5
5
  # nothing is linked, and MSVC-only behaviour is not exercised.
6
6
  #
7
7
  # sh tools/syntax-check.sh (needs: clang++, node_modules/node-addon-api, Node headers)
8
+ #
9
+ # The SDK root is passed with -iquote, not -I: the vendor headers include each other with quoted
10
+ # paths relative to that root ("BaseSDK/Deploy/Include/…"), and a plain -I would also let the C++
11
+ # standard library's `#include <version>` resolve to the drop's VERSION file on a case-insensitive
12
+ # file system (macOS, Windows). binding.gyp's include_dirs are unaffected — MSVC builds have passed
13
+ # with VERSION present.
8
14
  set -eu
9
15
 
10
16
  ROOT=$(cd "$(dirname "$0")/.." && pwd)
@@ -29,6 +35,6 @@ clang++ -fsyntax-only -std=c++17 -fshort-wchar -fdeclspec -fms-extensions \
29
35
  -Wno-ignored-attributes -Wno-microsoft -Wno-pragma-pack \
30
36
  -DSTOVE_PCSDK_SYNTAX_CHECK -DNAPI_VERSION=8 -DNAPI_DISABLE_CPP_EXCEPTIONS \
31
37
  -I "$STUB" -I "$NAPI_INC" -I "$NODE_INC" \
32
- -I "$SDK_DIR" -I "$SDK_DIR/BaseSDK/Deploy/Include" -I "$SDK_DIR/OwnershipSDK/Deploy/Include" -I "$SDK_DIR/GameSupportSDK/Deploy/Include" \
38
+ -iquote "$SDK_DIR" -I "$SDK_DIR/BaseSDK/Deploy/Include" -I "$SDK_DIR/OwnershipSDK/Deploy/Include" -I "$SDK_DIR/GameSupportSDK/Deploy/Include" \
33
39
  "$ROOT/src/stovesdk.cc"
34
40
  echo "[syntax-check] OK: src/stovesdk.cc parses against the SDK headers at $SDK_DIR"