windows-development-sandbox 1.17.0 → 1.18.0

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 (319) hide show
  1. package/README.md +92 -92
  2. package/config.example.json +97 -4
  3. package/dist/app/features/accounts/accounts.def.d.ts +116 -2
  4. package/dist/app/features/accounts/accounts.def.js +80 -12
  5. package/dist/app/features/accounts/accounts.run.d.ts +29 -0
  6. package/dist/app/features/accounts/accounts.run.js +36 -6
  7. package/dist/app/features/accounts/index.d.ts +2 -0
  8. package/dist/app/features/accounts/index.js +4 -1
  9. package/dist/app/features/accounts/services.def.d.ts +111 -0
  10. package/dist/app/features/accounts/services.def.js +187 -0
  11. package/dist/app/features/accounts/services.run.d.ts +65 -0
  12. package/dist/app/features/accounts/services.run.js +212 -0
  13. package/dist/app/features/defs.d.ts +112 -1
  14. package/dist/app/features/defs.js +5 -0
  15. package/dist/app/features/settings/categories.def.js +21 -4
  16. package/dist/app/registry.js +5 -1
  17. package/dist/bin/agent-sandbox.js +2 -2
  18. package/dist/bin/{chunk-QLGGAG3D.js → chunk-BKF43GJ3.js} +51 -16
  19. package/dist/bin/{chunk-REE3EMHD.js → chunk-YD3A67RS.js} +3812 -1702
  20. package/dist/bin/{chunk-XYUVIVJT.js → chunk-Z5D32FN3.js} +1 -1
  21. package/dist/bin/claude-host.js +2 -2
  22. package/dist/bin/claude-sbx.js +2 -2
  23. package/dist/bin/codex-sbx.js +2 -2
  24. package/dist/bin/gemini-sbx.js +2 -2
  25. package/dist/bin/sandbox.js +2 -2
  26. package/dist/bin/sbx-hide.js +2 -2
  27. package/dist/bin/sbx-show.js +2 -2
  28. package/dist/cli/commands/launch/launch.js +30 -1
  29. package/dist/cli/manifest.d.ts +93 -65
  30. package/dist/cli/manifest.js +23 -5
  31. package/dist/cli/menu/pages/accounts-page.js +31 -16
  32. package/dist/cli/services/accounts-service.d.ts +14 -4
  33. package/dist/cli/services/accounts-service.js +19 -6
  34. package/dist/cli/services/data-collect.js +26 -1
  35. package/dist/core/config/defaults.js +47 -4
  36. package/dist/core/detect/toolchains.d.ts +15 -1
  37. package/dist/core/detect/toolchains.js +45 -12
  38. package/dist/core/local-config/overrides.js +6 -0
  39. package/dist/core/schema/accounts.d.ts +27 -0
  40. package/dist/core/schema/accounts.js +32 -0
  41. package/dist/core/schema/config.d.ts +239 -0
  42. package/dist/core/schema/config.js +6 -0
  43. package/dist/core/schema/pc-installs.d.ts +7 -0
  44. package/dist/core/schema/pc-installs.js +17 -0
  45. package/dist/core/schema/toolchains.d.ts +61 -0
  46. package/dist/core/schema/toolchains.js +106 -0
  47. package/dist/core/types.d.ts +18 -0
  48. package/dist/guest/accounts/adapter.d.ts +25 -0
  49. package/dist/guest/accounts/adapter.js +21 -0
  50. package/dist/guest/accounts/adapters.d.ts +5 -0
  51. package/dist/guest/accounts/adapters.js +18 -0
  52. package/dist/guest/accounts/apply.d.ts +41 -0
  53. package/dist/guest/accounts/apply.js +47 -0
  54. package/dist/guest/accounts/kinds/appstore.d.ts +2 -0
  55. package/dist/guest/accounts/kinds/appstore.js +7 -0
  56. package/dist/guest/accounts/kinds/cfx.d.ts +2 -0
  57. package/dist/guest/accounts/kinds/cfx.js +7 -0
  58. package/dist/guest/accounts/kinds/epic.d.ts +2 -0
  59. package/dist/guest/accounts/kinds/epic.js +26 -0
  60. package/dist/guest/accounts/kinds/expo.d.ts +2 -0
  61. package/dist/guest/accounts/kinds/expo.js +7 -0
  62. package/dist/guest/accounts/kinds/keystore.d.ts +2 -0
  63. package/dist/guest/accounts/kinds/keystore.js +7 -0
  64. package/dist/guest/accounts/kinds/play.d.ts +2 -0
  65. package/dist/guest/accounts/kinds/play.js +7 -0
  66. package/dist/guest/accounts/kinds/steam.d.ts +4 -0
  67. package/dist/guest/accounts/kinds/steam.js +17 -0
  68. package/dist/guest/accounts/kinds/unity.d.ts +2 -0
  69. package/dist/guest/accounts/kinds/unity.js +7 -0
  70. package/dist/guest/accounts/watch.d.ts +28 -0
  71. package/dist/guest/accounts/watch.js +78 -0
  72. package/dist/guest/agents/agent-files.d.ts +6 -0
  73. package/dist/guest/agents/agent-files.js +14 -0
  74. package/dist/guest/agents/local-state.d.ts +2 -0
  75. package/dist/guest/agents/local-state.js +13 -1
  76. package/dist/guest/apps/resolvers-gamedev.d.ts +2 -0
  77. package/dist/guest/apps/resolvers-gamedev.js +1 -0
  78. package/dist/guest/apps/resolvers-mobile.d.ts +2 -0
  79. package/dist/guest/apps/resolvers-mobile.js +1 -0
  80. package/dist/guest/apps/resolvers.js +4 -0
  81. package/dist/guest/boot/boot.d.ts +5 -0
  82. package/dist/guest/boot/boot.js +36 -1
  83. package/dist/guest/daemon/accounts.d.ts +3 -0
  84. package/dist/guest/daemon/accounts.js +25 -0
  85. package/dist/guest/daemon/daemon.js +2 -0
  86. package/dist/guest/daemon/idle-shutdown.js +3 -1
  87. package/dist/guest/daemon/sessions.js +2 -0
  88. package/dist/guest/devtools/android.d.ts +2 -0
  89. package/dist/guest/devtools/android.js +7 -0
  90. package/dist/guest/devtools/devtool.d.ts +9 -0
  91. package/dist/guest/devtools/devtool.js +5 -0
  92. package/dist/guest/devtools/fivem.d.ts +2 -0
  93. package/dist/guest/devtools/fivem.js +7 -0
  94. package/dist/guest/devtools/registry.d.ts +6 -0
  95. package/dist/guest/devtools/registry.js +12 -0
  96. package/dist/guest/devtools/steam.d.ts +2 -0
  97. package/dist/guest/devtools/steam.js +228 -0
  98. package/dist/guest/devtools/unity.d.ts +2 -0
  99. package/dist/guest/devtools/unity.js +7 -0
  100. package/dist/guest/entries/daemon.js +2 -0
  101. package/dist/guest/entries/tool.d.ts +1 -0
  102. package/dist/guest/entries/tool.js +17 -0
  103. package/dist/guest/toolchains/engines/godot.d.ts +4 -0
  104. package/dist/guest/toolchains/engines/godot.js +4 -0
  105. package/dist/guest/toolchains/engines/unity-release.d.ts +15 -0
  106. package/dist/guest/toolchains/engines/unity-release.js +6 -0
  107. package/dist/guest/toolchains/engines/unity.d.ts +4 -0
  108. package/dist/guest/toolchains/engines/unity.js +4 -0
  109. package/dist/guest/toolchains/engines/unreal-engines.d.ts +4 -0
  110. package/dist/guest/toolchains/engines/unreal-engines.js +4 -0
  111. package/dist/guest/toolchains/fivem/artifacts.d.ts +6 -0
  112. package/dist/guest/toolchains/fivem/artifacts.js +7 -0
  113. package/dist/guest/toolchains/fivem/fivem.d.ts +4 -0
  114. package/dist/guest/toolchains/fivem/fivem.js +4 -0
  115. package/dist/guest/toolchains/installers.d.ts +12 -0
  116. package/dist/guest/toolchains/installers.js +39 -0
  117. package/dist/guest/toolchains/languages/java.js +1 -1
  118. package/dist/guest/toolchains/languages/kotlin.d.ts +3 -0
  119. package/dist/guest/toolchains/languages/kotlin.js +6 -0
  120. package/dist/guest/toolchains/mobile/android-repo.d.ts +11 -0
  121. package/dist/guest/toolchains/mobile/android-repo.js +10 -0
  122. package/dist/guest/toolchains/mobile/android.d.ts +17 -0
  123. package/dist/guest/toolchains/mobile/android.js +16 -0
  124. package/dist/guest/toolchains/mobile/dart.d.ts +4 -0
  125. package/dist/guest/toolchains/mobile/dart.js +4 -0
  126. package/dist/guest/toolchains/mobile/flutter.d.ts +4 -0
  127. package/dist/guest/toolchains/mobile/flutter.js +4 -0
  128. package/dist/guest/toolchains/mobile/react-native.d.ts +4 -0
  129. package/dist/guest/toolchains/mobile/react-native.js +4 -0
  130. package/dist/guest/toolchains/native/unreal.d.ts +3 -7
  131. package/dist/guest/toolchains/native/unreal.js +16 -155
  132. package/dist/guest/toolchains/native/visual-studio.d.ts +15 -0
  133. package/dist/guest/toolchains/native/visual-studio.js +159 -0
  134. package/dist/guest/toolchains/native/vs-request.d.ts +19 -0
  135. package/dist/guest/toolchains/native/vs-request.js +46 -0
  136. package/dist/guest/toolchains/not-yet.d.ts +2 -0
  137. package/dist/guest/toolchains/not-yet.js +6 -0
  138. package/dist/guest/toolchains/persisted.d.ts +48 -0
  139. package/dist/guest/toolchains/persisted.js +159 -0
  140. package/dist/guest/toolchains/plan.js +80 -4
  141. package/dist/guest/toolchains/steam/apps.d.ts +27 -0
  142. package/dist/guest/toolchains/steam/apps.js +112 -0
  143. package/dist/guest/toolchains/steam/client.d.ts +5 -0
  144. package/dist/guest/toolchains/steam/client.js +67 -0
  145. package/dist/guest/toolchains/steam/commands.d.ts +58 -0
  146. package/dist/guest/toolchains/steam/commands.js +155 -0
  147. package/dist/guest/toolchains/steam/steam.d.ts +4 -0
  148. package/dist/guest/toolchains/steam/steam.js +61 -0
  149. package/dist/guest/toolchains/steam/steamcmd-install.d.ts +29 -0
  150. package/dist/guest/toolchains/steam/steamcmd-install.js +177 -0
  151. package/dist/guest/toolchains/steam/steamcmd.d.ts +6 -0
  152. package/dist/guest/toolchains/steam/steamcmd.js +23 -0
  153. package/dist/guest/toolchains/step.d.ts +10 -0
  154. package/dist/host/accounts/deliver.d.ts +26 -0
  155. package/dist/host/accounts/deliver.js +80 -0
  156. package/dist/host/accounts/handoff.d.ts +17 -0
  157. package/dist/host/accounts/handoff.js +110 -0
  158. package/dist/host/accounts/index.d.ts +6 -0
  159. package/dist/host/accounts/index.js +8 -0
  160. package/dist/host/accounts/kinds/appstore.d.ts +2 -0
  161. package/dist/host/accounts/kinds/appstore.js +10 -0
  162. package/dist/host/accounts/kinds/cfx.d.ts +2 -0
  163. package/dist/host/accounts/kinds/cfx.js +7 -0
  164. package/dist/host/accounts/kinds/epic.d.ts +2 -0
  165. package/dist/host/accounts/kinds/epic.js +15 -0
  166. package/dist/host/accounts/kinds/expo.d.ts +2 -0
  167. package/dist/host/accounts/kinds/expo.js +7 -0
  168. package/dist/host/accounts/kinds/keystore.d.ts +2 -0
  169. package/dist/host/accounts/kinds/keystore.js +7 -0
  170. package/dist/host/accounts/kinds/play.d.ts +2 -0
  171. package/dist/host/accounts/kinds/play.js +7 -0
  172. package/dist/host/accounts/kinds/steam.d.ts +6 -0
  173. package/dist/host/accounts/kinds/steam.js +31 -0
  174. package/dist/host/accounts/kinds/unity.d.ts +2 -0
  175. package/dist/host/accounts/kinds/unity.js +13 -0
  176. package/dist/host/accounts/leases.d.ts +40 -0
  177. package/dist/host/accounts/leases.js +62 -0
  178. package/dist/host/accounts/source.d.ts +21 -0
  179. package/dist/host/accounts/source.js +15 -0
  180. package/dist/host/accounts/sources.d.ts +5 -0
  181. package/dist/host/accounts/sources.js +18 -0
  182. package/dist/host/accounts/store.d.ts +23 -0
  183. package/dist/host/accounts/store.js +74 -0
  184. package/dist/host/audit/log.d.ts +1 -1
  185. package/dist/host/launch/generate.d.ts +2 -0
  186. package/dist/host/launch/generate.js +16 -1
  187. package/dist/host/launch/hints/android.d.ts +3 -0
  188. package/dist/host/launch/hints/android.js +3 -0
  189. package/dist/host/launch/hints/engines.d.ts +3 -0
  190. package/dist/host/launch/hints/engines.js +3 -0
  191. package/dist/host/launch/hints/fivem.d.ts +3 -0
  192. package/dist/host/launch/hints/fivem.js +3 -0
  193. package/dist/host/launch/hints/hint.d.ts +2 -0
  194. package/dist/host/launch/hints/hint.js +1 -0
  195. package/dist/host/launch/hints/index.d.ts +5 -0
  196. package/dist/host/launch/hints/index.js +29 -0
  197. package/dist/host/launch/hints/mobile.d.ts +3 -0
  198. package/dist/host/launch/hints/mobile.js +3 -0
  199. package/dist/host/launch/hints/steam.d.ts +7 -0
  200. package/dist/host/launch/hints/steam.js +59 -0
  201. package/dist/host/launch/index.d.ts +2 -2
  202. package/dist/host/launch/index.js +2 -2
  203. package/dist/host/launch/mappings.js +2 -0
  204. package/dist/host/launch/session-file.js +2 -0
  205. package/dist/host/launch/stage.js +10 -1
  206. package/dist/host/launch/toolchains.d.ts +7 -0
  207. package/dist/host/launch/toolchains.js +11 -0
  208. package/dist/host/pc-installs/discover.d.ts +19 -0
  209. package/dist/host/pc-installs/discover.js +59 -0
  210. package/dist/host/pc-installs/epic.d.ts +2 -0
  211. package/dist/host/pc-installs/epic.js +7 -0
  212. package/dist/host/pc-installs/index.d.ts +3 -0
  213. package/dist/host/pc-installs/index.js +2 -0
  214. package/dist/host/pc-installs/source.d.ts +9 -0
  215. package/dist/host/pc-installs/source.js +1 -0
  216. package/dist/host/pc-installs/steam.d.ts +16 -0
  217. package/dist/host/pc-installs/steam.js +143 -0
  218. package/dist/host/pc-installs/unity.d.ts +2 -0
  219. package/dist/host/pc-installs/unity.js +7 -0
  220. package/dist/host/pc-installs/unreal.d.ts +2 -0
  221. package/dist/host/pc-installs/unreal.js +7 -0
  222. package/dist/host/profile/accounts.d.ts +21 -6
  223. package/dist/host/profile/accounts.js +108 -28
  224. package/dist/host/profile/claude-login.d.ts +32 -2
  225. package/dist/host/profile/claude-login.js +100 -8
  226. package/dist/host/profile/index.d.ts +1 -1
  227. package/dist/host/profile/index.js +1 -1
  228. package/dist/host/secrets/launch-secrets.js +1 -0
  229. package/dist/runtime/agent.js +321 -9
  230. package/dist/runtime/daemon.js +1987 -595
  231. package/dist/runtime/db.js +334 -11
  232. package/dist/runtime/main.js +2530 -709
  233. package/dist/runtime/netproxy.js +321 -9
  234. package/dist/runtime/tool.js +28549 -0
  235. package/dist/runtime/view.js +321 -9
  236. package/dist/selftest/accounts.test.js +172 -1
  237. package/dist/selftest/agent-paths.test.js +13 -1
  238. package/dist/selftest/android.test.d.ts +2 -0
  239. package/dist/selftest/android.test.js +7 -0
  240. package/dist/selftest/app.test.js +1 -1
  241. package/dist/selftest/engines.test.d.ts +2 -0
  242. package/dist/selftest/engines.test.js +7 -0
  243. package/dist/selftest/fivem.test.d.ts +2 -0
  244. package/dist/selftest/fivem.test.js +7 -0
  245. package/dist/selftest/generate.test.js +1 -1
  246. package/dist/selftest/index.js +18 -0
  247. package/dist/selftest/menu-screens.js +1 -0
  248. package/dist/selftest/menu.test.js +2 -1
  249. package/dist/selftest/mobile.test.d.ts +2 -0
  250. package/dist/selftest/mobile.test.js +7 -0
  251. package/dist/selftest/pc-installs.test.d.ts +2 -0
  252. package/dist/selftest/pc-installs.test.js +111 -0
  253. package/dist/selftest/persisted-installs.test.d.ts +2 -0
  254. package/dist/selftest/persisted-installs.test.js +107 -0
  255. package/dist/selftest/projects.test.js +6 -4
  256. package/dist/selftest/service-accounts.test.d.ts +2 -0
  257. package/dist/selftest/service-accounts.test.js +320 -0
  258. package/dist/selftest/steam.test.d.ts +2 -0
  259. package/dist/selftest/steam.test.js +316 -0
  260. package/dist/selftest/storage-helper.test.js +31 -8
  261. package/dist/selftest/support/engine-golden.js +3 -0
  262. package/dist/selftest/support/fixtures.d.ts +87 -0
  263. package/dist/selftest/support/fixtures.js +4 -3
  264. package/dist/selftest/support/pc-memory.d.ts +2 -0
  265. package/dist/selftest/support/pc-memory.js +17 -0
  266. package/dist/selftest/support/web-preview.js +20 -1
  267. package/dist/selftest/unity.test.d.ts +2 -0
  268. package/dist/selftest/unity.test.js +7 -0
  269. package/dist/shared/accounts/fingerprint.d.ts +1 -0
  270. package/dist/shared/accounts/fingerprint.js +20 -0
  271. package/dist/shared/accounts/index.d.ts +5 -0
  272. package/dist/shared/accounts/index.js +7 -0
  273. package/dist/shared/accounts/kinds/epic.d.ts +10 -0
  274. package/dist/shared/accounts/kinds/epic.js +56 -0
  275. package/dist/shared/accounts/kinds/steam.d.ts +28 -0
  276. package/dist/shared/accounts/kinds/steam.js +99 -0
  277. package/dist/shared/accounts/kinds.d.ts +41 -0
  278. package/dist/shared/accounts/kinds.js +154 -0
  279. package/dist/shared/accounts/payloads.d.ts +151 -0
  280. package/dist/shared/accounts/payloads.js +128 -0
  281. package/dist/shared/apps/catalogue-gamedev.d.ts +2 -0
  282. package/dist/shared/apps/catalogue-gamedev.js +1 -0
  283. package/dist/shared/apps/catalogue-mobile.d.ts +2 -0
  284. package/dist/shared/apps/catalogue-mobile.js +1 -0
  285. package/dist/shared/apps/catalogue-modding.d.ts +2 -0
  286. package/dist/shared/apps/catalogue-modding.js +1 -0
  287. package/dist/shared/apps/catalogue.d.ts +1 -0
  288. package/dist/shared/apps/catalogue.js +6 -1
  289. package/dist/shared/channel/schema.d.ts +183 -0
  290. package/dist/shared/channel/secrets.d.ts +42 -0
  291. package/dist/shared/channel/secrets.js +3 -0
  292. package/dist/shared/download/downloader.d.ts +7 -0
  293. package/dist/shared/download/downloader.js +10 -3
  294. package/dist/shared/installs.d.ts +19 -0
  295. package/dist/shared/installs.js +13 -0
  296. package/dist/shared/io/bounded.d.ts +6 -0
  297. package/dist/shared/io/bounded.js +38 -0
  298. package/dist/shared/io/index.d.ts +2 -0
  299. package/dist/shared/io/index.js +2 -0
  300. package/dist/shared/io/text.d.ts +9 -0
  301. package/dist/shared/io/text.js +18 -0
  302. package/dist/shared/paths/guest-paths.js +1 -1
  303. package/dist/shared/paths/index.d.ts +1 -0
  304. package/dist/shared/paths/index.js +1 -0
  305. package/dist/shared/paths/pc-installs.d.ts +20 -0
  306. package/dist/shared/paths/pc-installs.js +19 -0
  307. package/dist/shared/vdf.d.ts +25 -0
  308. package/dist/shared/vdf.js +151 -0
  309. package/dist/sync/sync-cycle.js +9 -0
  310. package/dist/web/index.html +1 -1
  311. package/dist/web/{main-JTH3FT4O.js → main-CWIF4VQI.js} +63 -61
  312. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  313. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  314. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  315. package/package.json +1 -1
  316. package/sandbox/scripts/add-firewall-port.ps1 +13 -0
  317. package/sandbox/templates/fivem/README.txt +2 -0
  318. package/schemas/config.schema.json +485 -0
  319. package/schemas/sandbox-config.schema.json +416 -0
@@ -0,0 +1,155 @@
1
+ // Where Steam things live in the sandbox and the SteamCMD command lines sbx-steam runs. Every value that goes on a
2
+ // SteamCMD command line is checked first (an app or item ID is digits, a branch or account name a plain word, a
3
+ // script a full path to an existing file): `+` starts a new SteamCMD command, so nothing else may reach it. No runtime
4
+ // imports: the self-test loads this file.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { STEAM_ACCOUNT } from '../../../shared/accounts/kinds/steam.js';
8
+ import { parseVdf } from '../../../shared/vdf.js';
9
+ import { dataDir, toolsDir } from '../../runtime/paths.js';
10
+ /** Where SteamCMD runs from in the sandbox (its own disk: its config\ holds the login). */
11
+ export const STEAMCMD_DIR = 'C:\\SteamCMD';
12
+ /** Valve publishes no checksum for it: steamcmd.exe inside must be Valve-signed. */
13
+ export const STEAMCMD_URL = 'https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip';
14
+ export const STEAM_CLIENT_URL = 'https://steamcdn-a.akamaihd.net/client/installer/SteamSetup.exe';
15
+ /** SteamCMD's binaries, kept between sandboxes (never its config\ or sentry files). */
16
+ export function steamCmdStore(tools = toolsDir) {
17
+ return path.join(tools, 'steamcmd');
18
+ }
19
+ /** Games and dedicated servers that sbx-steam app downloads: tools\steam-apps\<appid>. */
20
+ export function steamAppsRoot(tools = toolsDir) {
21
+ return path.join(tools, 'steam-apps');
22
+ }
23
+ export function steamAppDir(appId, tools = toolsDir) {
24
+ return path.join(steamAppsRoot(tools), checkAppId(appId));
25
+ }
26
+ /** Workshop items that sbx-steam workshop get downloads (a cache on the data disk). */
27
+ export function workshopCache(data = dataDir) {
28
+ return path.join(data, 'cache', 'steam-workshop');
29
+ }
30
+ /** Where a downloaded workshop item is. */
31
+ export function workshopItemDir(cache, appId, item) {
32
+ return path.join(cache, 'steamapps', 'workshop', 'content', checkAppId(appId), checkItemId(item));
33
+ }
34
+ /** The Steam client (toolchains.steam.client), which updates itself in place. */
35
+ export function steamClientDir(tools = toolsDir) {
36
+ return path.join(tools, 'steam');
37
+ }
38
+ function checked(value, pattern, what) {
39
+ if (!pattern.test(value))
40
+ throw new Error('not a ' + what + ': ' + JSON.stringify(value.slice(0, 80)));
41
+ return value;
42
+ }
43
+ export const checkAppId = (v) => checked(v, /^[1-9]\d{0,9}$/, 'Steam app ID');
44
+ export const checkItemId = (v) => checked(v, /^[1-9]\d{0,19}$/, 'workshop item ID');
45
+ export const checkBranch = (v) => checked(v, /^[A-Za-z0-9._-]{1,64}$/, 'branch name');
46
+ /** A Steam account name, or "anonymous" (dedicated servers and free tools). */
47
+ export const checkLogin = (v) => checked(v, STEAM_ACCOUNT, 'Steam account name');
48
+ /** A full path SteamCMD can take as one value: no quotes, no `+` at the start of a part, no line breaks. */
49
+ export function checkPathValue(p, what) {
50
+ if (!path.isAbsolute(p) || /["\r\n\0]/.test(p) || /(^|[\\/ ])\+/.test(p))
51
+ throw new Error(what + ' cannot be given to SteamCMD: ' + JSON.stringify(p.slice(0, 200)));
52
+ return p;
53
+ }
54
+ /** Downloads or updates an app (a game, a dedicated server) into a folder. force_install_dir comes before login. */
55
+ export function appUpdateArgs(o) {
56
+ return [
57
+ '+force_install_dir',
58
+ checkPathValue(o.dir, 'The folder'),
59
+ '+login',
60
+ checkLogin(o.login),
61
+ '+app_update',
62
+ checkAppId(o.appId),
63
+ ...(o.beta ? ['-beta', checkBranch(o.beta)] : []),
64
+ ...(o.validate ? ['validate'] : []),
65
+ '+quit',
66
+ ];
67
+ }
68
+ /** Downloads workshop items of an app into <dir>\steamapps\workshop\content\<app>\<item>. */
69
+ export function workshopGetArgs(o) {
70
+ if (!o.items.length)
71
+ throw new Error('no workshop item given');
72
+ if (o.items.length > 100)
73
+ throw new Error('at most 100 workshop items at once');
74
+ const app = checkAppId(o.appId);
75
+ return [
76
+ '+force_install_dir',
77
+ checkPathValue(o.dir, 'The folder'),
78
+ '+login',
79
+ checkLogin(o.login),
80
+ ...o.items.flatMap((item) => [
81
+ '+workshop_download_item',
82
+ app,
83
+ checkItemId(item),
84
+ ...(o.validate ? ['validate'] : []),
85
+ ]),
86
+ '+quit',
87
+ ];
88
+ }
89
+ /** Uploads a workshop item described by a workshop_item .vdf (a real account: not anonymous). */
90
+ export function workshopPushArgs(login, script) {
91
+ if (login === 'anonymous')
92
+ throw new Error('uploading needs your Steam account, not anonymous');
93
+ return ['+login', checkLogin(login), '+workshop_build_item', checkPathValue(script, 'The script'), '+quit'];
94
+ }
95
+ /** Uploads a build described by an app_build .vdf (ContentBuilder). */
96
+ export function appBuildArgs(login, script) {
97
+ if (login === 'anonymous')
98
+ throw new Error('uploading needs your Steam account, not anonymous');
99
+ return ['+login', checkLogin(login), '+run_app_build', checkPathValue(script, 'The script'), '+quit'];
100
+ }
101
+ /** Signs in (SteamCMD asks for the password and the Steam Guard code itself) and keeps the login. */
102
+ export function loginArgs(login) {
103
+ if (login === 'anonymous')
104
+ throw new Error('anonymous needs no sign-in');
105
+ return ['+login', checkLogin(login), '+quit'];
106
+ }
107
+ /** At most this much of a build or workshop script is read. */
108
+ export const MAX_SCRIPT_BYTES = 256 * 1024;
109
+ /** What a SteamCMD script is: its root block ("AppBuild" or "workshopitem"), or null. */
110
+ export function scriptKind(text) {
111
+ const root = parseVdf(text, { maxBytes: MAX_SCRIPT_BYTES, escapes: false });
112
+ const first = root ? Object.keys(root)[0]?.toLowerCase() : undefined;
113
+ return first === 'appbuild' || first === 'workshopitem' ? first : null;
114
+ }
115
+ /** An app_build script's text with Preview on and/or a description (null when it is not an app_build script). */
116
+ export function withBuildSettings(text, o) {
117
+ if (scriptKind(text) !== 'appbuild')
118
+ return null;
119
+ if (o.desc !== undefined && !/^[^"\r\n\\]{1,200}$/.test(o.desc))
120
+ throw new Error('a build description is one line of at most 200 characters, without quotes or backslashes');
121
+ let out = text;
122
+ const set = (key, value) => {
123
+ const existing = new RegExp('"' + key + '"[ \\t]+"[^"\\r\\n]*"', 'i');
124
+ if (existing.test(out)) {
125
+ out = out.replace(existing, '"' + key + '" "' + value + '"');
126
+ return;
127
+ }
128
+ const at = out.toLowerCase().indexOf('"appbuild"');
129
+ const brace = at < 0 ? -1 : out.indexOf('{', at);
130
+ if (brace < 0)
131
+ throw new Error('the app_build script has no AppBuild block');
132
+ out = out.slice(0, brace + 1) + '\n\t"' + key + '" "' + value + '"' + out.slice(brace + 1);
133
+ };
134
+ if (o.preview)
135
+ set('Preview', '1');
136
+ if (o.desc !== undefined)
137
+ set('Desc', o.desc);
138
+ return out;
139
+ }
140
+ /** The script's text (bounded), or an error a person can act on. */
141
+ export function readScript(file) {
142
+ let size;
143
+ try {
144
+ const stat = fs.lstatSync(file);
145
+ if (!stat.isFile())
146
+ throw new Error('not a file');
147
+ size = stat.size;
148
+ }
149
+ catch {
150
+ throw new Error('cannot read ' + file + ' (missing, a link, or not a file)');
151
+ }
152
+ if (size > MAX_SCRIPT_BYTES)
153
+ throw new Error(file + ' is bigger than ' + MAX_SCRIPT_BYTES / 1024 + ' KB');
154
+ return fs.readFileSync(file, 'utf8');
155
+ }
@@ -0,0 +1,4 @@
1
+ import type { Toolchains } from '../../../core/types.js';
2
+ import type { ToolCtx } from '../step.js';
3
+ import type { ToolEnv } from '../tool-env.js';
4
+ export declare function setupSteam(env: ToolEnv, config: Toolchains['steam'], ctx: ToolCtx): Promise<void>;
@@ -0,0 +1,61 @@
1
+ // Steam (toolchains.steam): SteamCMD in C:\SteamCMD each boot (binaries kept in tools\steamcmd), steamcmd on PATH,
2
+ // the Steam client when asked, STEAMWORKS_SDK from sdkFolder, and a word on the PC's Steam libraries mapped read-only.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { hasCachedLogin, readSteamAccountFile, steamConfigFile } from '../../../shared/accounts/kinds/steam.js';
6
+ import { readBoundedFile } from '../../../shared/io/bounded.js';
7
+ import { log } from '../../runtime/log.js';
8
+ import { dryRun } from '../../runtime/paths.js';
9
+ import { projectWorkspace } from '../../runtime/project-context.js';
10
+ import { ensureSteamClient, installedSteamClient } from './client.js';
11
+ import { STEAMCMD_DIR } from './commands.js';
12
+ import { installSteamCmd } from './steamcmd.js';
13
+ function signedInAs() {
14
+ const bytes = readBoundedFile(steamConfigFile(STEAMCMD_DIR), 256 * 1024);
15
+ if (!bytes || !hasCachedLogin(bytes.toString('utf8')))
16
+ return null;
17
+ return readSteamAccountFile(STEAMCMD_DIR) ?? 'your account';
18
+ }
19
+ /** STEAMWORKS_SDK from toolchains.steam.sdkFolder (relative to the project), when the folder is there. */
20
+ function sdkFolder(env, setting) {
21
+ if (!setting.trim())
22
+ return;
23
+ const dir = path.resolve(projectWorkspace(), setting.trim());
24
+ let ok = false;
25
+ try {
26
+ ok = fs.statSync(dir).isDirectory();
27
+ }
28
+ catch {
29
+ // Not there: said below.
30
+ }
31
+ if (!ok) {
32
+ log('Steamworks SDK: ' + dir + ' is not there (toolchains.steam.sdkFolder); STEAMWORKS_SDK is not set');
33
+ env.summary.push('steamworks sdk: ' + dir + ' not found');
34
+ return;
35
+ }
36
+ env.vars.STEAMWORKS_SDK = dir;
37
+ env.summary.push('steamworks sdk: STEAMWORKS_SDK=' + dir);
38
+ }
39
+ export async function setupSteam(env, config, ctx) {
40
+ if (dryRun) {
41
+ log('(dry run) would set up SteamCMD' + (config.client ? ' and the Steam client' : ''));
42
+ return;
43
+ }
44
+ const exe = await installSteamCmd();
45
+ // Appended: C:\SteamCMD also holds Steam's own DLLs, which must not shadow a game's.
46
+ env.append.push(STEAMCMD_DIR);
47
+ env.vars.STEAMCMD = exe;
48
+ const user = signedInAs();
49
+ env.summary.push('steamcmd: C:\\SteamCMD, ' + (user ? 'signed in as ' + user : 'not signed in (sbx-steam login <account>)'));
50
+ sdkFolder(env, config.sdkFolder);
51
+ const fromPc = ctx.pcInstalls.items.filter((i) => i.kind === 'steam-game' || i.kind === 'steam-library');
52
+ if (fromPc.length)
53
+ env.summary.push('steam: ' + fromPc.length + " of this PC's games or libraries, read-only in C:\\PcInstalls\\steam");
54
+ if (!config.client)
55
+ return;
56
+ if (ctx.duringSession && !installedSteamClient()) {
57
+ env.summary.push('steam client: restart the sandbox to install it');
58
+ return;
59
+ }
60
+ await ensureSteamClient(env);
61
+ }
@@ -0,0 +1,29 @@
1
+ import { type PersistedDeps } from '../persisted.js';
2
+ /** Room SteamCMD needs: the zip is small, its first run fetches ~300 MB. */
3
+ export declare const STEAMCMD_NEED_BYTES: number;
4
+ export declare const STEAMCMD_UPDATE_TIMEOUT_MS: number;
5
+ export interface RunResult {
6
+ readonly code: number;
7
+ readonly timedOut: boolean;
8
+ }
9
+ export interface SteamCmdDeps extends PersistedDeps {
10
+ /** Whether a file is Valve-signed (required: the default needs the guest runtime). */
11
+ readonly signed: (file: string) => boolean;
12
+ /** tools\steamcmd. */
13
+ readonly storeDir?: string;
14
+ /** C:\SteamCMD. */
15
+ readonly workDir?: string;
16
+ readonly download?: (url: string, file: string, verify: (part: string) => Promise<void>) => Promise<void>;
17
+ readonly extract?: (archive: string, destination: string) => Promise<void>;
18
+ readonly run?: (exe: string, args: string[], cwd: string, timeoutMs: number) => Promise<RunResult>;
19
+ readonly updateTimeoutMs?: number;
20
+ }
21
+ /** Runs a program with its output hidden; it is stopped after `timeoutMs`. */
22
+ export declare function runWithTimeout(exe: string, args: string[], cwd: string, timeoutMs: number): Promise<RunResult>;
23
+ /** steamcmd.zip into `store`: kept only when steamcmd.exe in it is Valve-signed. */
24
+ export declare function fetchSteamCmd(store: string, deps: SteamCmdDeps): Promise<void>;
25
+ /**
26
+ * steamcmd.exe in the working folder, ready to run: downloaded once, copied in, updated by its own first run (with a
27
+ * time limit), and its updated binaries kept for the next boot.
28
+ */
29
+ export declare function prepareSteamCmd(deps: SteamCmdDeps): Promise<string>;
@@ -0,0 +1,177 @@
1
+ // Getting SteamCMD ready, with every outside effect passed in (the self-test runs it with its own folders, download,
2
+ // signer check and runner): steamcmd.zip once into tools\steamcmd (Valve publishes no checksum, so steamcmd.exe in it
3
+ // must be Valve-signed before the zip is kept), a working copy in C:\SteamCMD every boot (config\ and the sentry
4
+ // files never leave the sandbox's own disk), its first-run self-update with a time limit, and the updated binaries
5
+ // kept for the next boot. No runtime imports.
6
+ import { spawn } from 'node:child_process';
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { extractArchive, moveIntoPlace, stagingFolder } from '../../../shared/download/archive.js';
10
+ import { downloader } from '../../../shared/download/downloader.js';
11
+ import { safeRemove } from '../../../shared/io/safe-remove.js';
12
+ import { assertRoom } from '../persisted.js';
13
+ import { STEAMCMD_DIR, STEAMCMD_URL, steamCmdStore } from './commands.js';
14
+ const MB = 1024 ** 2;
15
+ /** Room SteamCMD needs: the zip is small, its first run fetches ~300 MB. */
16
+ export const STEAMCMD_NEED_BYTES = 600 * MB;
17
+ export const STEAMCMD_UPDATE_TIMEOUT_MS = 15 * 60 * 1000;
18
+ /** What a SteamCMD folder keeps that is the sign-in or this sandbox's own: never copied to the data disk. */
19
+ const PRIVATE_DIRS = new Set(['config', 'logs', 'dumps', 'userdata', 'steamapps', 'appcache', 'depotcache']);
20
+ const isPrivate = (name) => PRIVATE_DIRS.has(name.toLowerCase()) || /^ssfn/i.test(name);
21
+ /** Files that show SteamCMD has finished its first run (it fetches these itself). */
22
+ const BOOTSTRAPPED = ['steamclient.dll', 'steamclient64.dll'];
23
+ /** Runs a program with its output hidden; it is stopped after `timeoutMs`. */
24
+ export function runWithTimeout(exe, args, cwd, timeoutMs) {
25
+ return new Promise((resolve, reject) => {
26
+ const child = spawn(exe, args, { cwd, stdio: 'ignore', windowsHide: true });
27
+ let timedOut = false;
28
+ const timer = setTimeout(() => {
29
+ timedOut = true;
30
+ child.kill();
31
+ }, timeoutMs);
32
+ child.once('error', (e) => {
33
+ clearTimeout(timer);
34
+ reject(e);
35
+ });
36
+ child.once('close', (code) => {
37
+ clearTimeout(timer);
38
+ resolve({ code: code ?? -1, timedOut });
39
+ });
40
+ });
41
+ }
42
+ const notSigned = (file) => new Error(path.basename(file) + ' is not signed by Valve: not running it (it is downloaded again next boot)');
43
+ /** steamcmd.zip into `store`: kept only when steamcmd.exe in it is Valve-signed. */
44
+ export async function fetchSteamCmd(store, deps) {
45
+ fs.mkdirSync(path.dirname(store), { recursive: true });
46
+ assertRoom(path.dirname(store), STEAMCMD_NEED_BYTES, 'SteamCMD', 'Free room on the data disk.', deps);
47
+ const work = stagingFolder(store);
48
+ const unpacked = path.join(work, 'x');
49
+ const extract = deps.extract ?? extractArchive;
50
+ try {
51
+ fs.mkdirSync(work, { recursive: true });
52
+ const verify = async (part) => {
53
+ await extract(part, unpacked);
54
+ const exe = path.join(unpacked, 'steamcmd.exe');
55
+ if (!fs.existsSync(exe))
56
+ throw new Error('steamcmd.zip has no steamcmd.exe');
57
+ if (!deps.signed(exe))
58
+ throw notSigned(exe);
59
+ };
60
+ const zip = path.join(work, 'steamcmd.zip');
61
+ if (deps.download)
62
+ await deps.download(STEAMCMD_URL, zip, verify);
63
+ else
64
+ await downloader.file(STEAMCMD_URL, zip, { verify });
65
+ moveIntoPlace(unpacked, store);
66
+ }
67
+ finally {
68
+ if (fs.existsSync(work))
69
+ safeRemove(work, path.dirname(work));
70
+ }
71
+ }
72
+ /** Copies a SteamCMD folder, leaving out the sign-in and this sandbox's own files at its top. */
73
+ async function copyBinaries(from, to) {
74
+ fs.mkdirSync(to, { recursive: true });
75
+ for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
76
+ if (isPrivate(entry.name) || entry.isSymbolicLink())
77
+ continue;
78
+ if (!entry.isFile() && !entry.isDirectory())
79
+ continue;
80
+ await fs.promises.cp(path.join(from, entry.name), path.join(to, entry.name), {
81
+ recursive: true,
82
+ force: true,
83
+ verbatimSymlinks: true,
84
+ filter: (source) => {
85
+ try {
86
+ return !fs.lstatSync(source).isSymbolicLink();
87
+ }
88
+ catch {
89
+ return false;
90
+ }
91
+ },
92
+ });
93
+ }
94
+ }
95
+ /** Size and time of SteamCMD's own top-level files: what changes when it updates itself. */
96
+ function snapshot(dir) {
97
+ try {
98
+ return fs
99
+ .readdirSync(dir, { withFileTypes: true })
100
+ .filter((e) => e.isFile() && !isPrivate(e.name) && /\.(exe|dll)$/i.test(e.name))
101
+ .map((e) => {
102
+ const s = fs.statSync(path.join(dir, e.name));
103
+ return e.name.toLowerCase() + ':' + s.size + ':' + Math.floor(s.mtimeMs / 1000);
104
+ })
105
+ .sort()
106
+ .join('|');
107
+ }
108
+ catch {
109
+ return '';
110
+ }
111
+ }
112
+ const bootstrapped = (dir) => BOOTSTRAPPED.some((f) => fs.existsSync(path.join(dir, f)));
113
+ /** Removes anything private that ended up in the kept copy (it must hold only binaries). */
114
+ function scrubStore(store) {
115
+ let names = [];
116
+ try {
117
+ names = fs.readdirSync(store);
118
+ }
119
+ catch {
120
+ return;
121
+ }
122
+ for (const name of names.filter(isPrivate))
123
+ safeRemove(path.join(store, name), store);
124
+ }
125
+ /**
126
+ * steamcmd.exe in the working folder, ready to run: downloaded once, copied in, updated by its own first run (with a
127
+ * time limit), and its updated binaries kept for the next boot.
128
+ */
129
+ export async function prepareSteamCmd(deps) {
130
+ const store = deps.storeDir ?? steamCmdStore(deps.toolsDir);
131
+ const work = deps.workDir ?? STEAMCMD_DIR;
132
+ const log = deps.log ?? (() => undefined);
133
+ const exe = path.join(work, 'steamcmd.exe');
134
+ // Set up already in this sandbox (a project that joins later): its files may be in use, so they are left alone.
135
+ if (fs.existsSync(exe) && bootstrapped(work)) {
136
+ if (!deps.signed(exe))
137
+ throw notSigned(exe);
138
+ return exe;
139
+ }
140
+ if (!fs.existsSync(path.join(store, 'steamcmd.exe'))) {
141
+ log('Downloading SteamCMD (~3 MB once; it fetches ~300 MB on its first run) ...');
142
+ await fetchSteamCmd(store, deps);
143
+ }
144
+ scrubStore(store);
145
+ await copyBinaries(store, work);
146
+ if (!deps.signed(exe)) {
147
+ safeRemove(store, path.dirname(store));
148
+ throw notSigned(exe);
149
+ }
150
+ const before = snapshot(work);
151
+ const run = deps.run ?? runWithTimeout;
152
+ const limit = deps.updateTimeoutMs ?? STEAMCMD_UPDATE_TIMEOUT_MS;
153
+ for (let attempt = 1; attempt <= 2; attempt++) {
154
+ log(attempt === 1 ? 'SteamCMD: checking for updates ...' : 'SteamCMD: trying its update once more ...');
155
+ const result = await run(exe, ['+quit'], work, limit);
156
+ if (result.timedOut)
157
+ throw new Error('SteamCMD did not finish updating itself in ' + Math.round(limit / 60000) + ' minutes');
158
+ if (bootstrapped(work))
159
+ break;
160
+ if (attempt === 2)
161
+ throw new Error('SteamCMD did not finish its first run (exit code ' + result.code + ')');
162
+ }
163
+ if (!deps.signed(exe))
164
+ throw notSigned(exe);
165
+ if (snapshot(work) !== before || !bootstrapped(store)) {
166
+ const staging = stagingFolder(store);
167
+ try {
168
+ await copyBinaries(work, staging);
169
+ moveIntoPlace(staging, store);
170
+ }
171
+ finally {
172
+ if (fs.existsSync(staging))
173
+ safeRemove(staging, path.dirname(staging));
174
+ }
175
+ }
176
+ return exe;
177
+ }
@@ -0,0 +1,6 @@
1
+ import type { ToolEnv } from '../tool-env.js';
2
+ export { STEAMCMD_DIR } from './commands.js';
3
+ /** steamcmd.exe ready to run; throws with the reason when it cannot be set up. */
4
+ export declare function installSteamCmd(): Promise<string>;
5
+ /** steamcmd.exe ready to run, or null when it could not be set up (the reason is in the log and the summary). */
6
+ export declare function ensureSteamCmd(env: ToolEnv): Promise<string | null>;
@@ -0,0 +1,23 @@
1
+ // SteamCMD: steamcmd.zip from Valve's CDN (no checksum published: steamcmd.exe must be Valve-signed), its binaries
2
+ // kept in tools\steamcmd and copied to C:\SteamCMD each boot (its config\ holds the login, which never sits on the
3
+ // data disk). The work itself is in steamcmd-install.ts; this file gives it the sandbox's log and signer check.
4
+ import { errMsg } from '../../../core/util/errors.js';
5
+ import { log } from '../../runtime/log.js';
6
+ import { SIGNERS, signedBy } from '../installers.js';
7
+ import { prepareSteamCmd } from './steamcmd-install.js';
8
+ export { STEAMCMD_DIR } from './commands.js';
9
+ /** steamcmd.exe ready to run; throws with the reason when it cannot be set up. */
10
+ export function installSteamCmd() {
11
+ return prepareSteamCmd({ signed: (file) => signedBy(file, SIGNERS.valve), log });
12
+ }
13
+ /** steamcmd.exe ready to run, or null when it could not be set up (the reason is in the log and the summary). */
14
+ export async function ensureSteamCmd(env) {
15
+ try {
16
+ return await installSteamCmd();
17
+ }
18
+ catch (e) {
19
+ log('SteamCMD: not set up: ' + errMsg(e));
20
+ env.summary.push('steamcmd: not set up (' + errMsg(e) + ')');
21
+ return null;
22
+ }
23
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Config, DatabasePlan, GitAccess } from '../../core/types.js';
2
+ import type { PcInstallManifest } from '../../shared/paths/pc-installs.js';
2
3
  import type { ToolEnv } from './tool-env.js';
3
4
  /** apps: the boot only (apps are machine-wide; a project that joins later shares them). */
4
5
  export interface ToolchainOptions {
@@ -8,6 +9,15 @@ export interface ToolchainOptions {
8
9
  refresh: boolean;
9
10
  databases: DatabasePlan[];
10
11
  apps?: Config['apps'];
12
+ /** pcInstalls: this PC's games, engines and editors mapped read-only (session.json). */
13
+ pcInstalls?: PcInstallManifest;
14
+ }
15
+ /** What a toolchain module's step gets besides its settings. */
16
+ export interface ToolCtx {
17
+ readonly refresh: boolean;
18
+ readonly pcInstalls: PcInstallManifest;
19
+ /** A project joins a running sandbox: machine-wide installs (Visual Studio) wait for a restart. */
20
+ readonly duringSession: boolean;
11
21
  }
12
22
  /** One installer the scheduler runs: a toolchain, the apps, or a database server. */
13
23
  export interface ToolchainStep {
@@ -0,0 +1,26 @@
1
+ import type { BootWant } from '../../core/local-config/index.js';
2
+ import type { Config } from '../../core/types.js';
3
+ import { type Payloads, type ServiceKind, type UsedBy } from '../../shared/accounts/index.js';
4
+ import { AccountLeases } from './leases.js';
5
+ import { ServiceAccountStore } from './store.js';
6
+ export interface AccountDelivery {
7
+ readonly payloads: Payloads;
8
+ /** The kinds whose lease this launch holds (the sandbox may hand them back). */
9
+ readonly leased: readonly ServiceKind[];
10
+ readonly notes: readonly string[];
11
+ }
12
+ export interface DeliveryInput {
13
+ readonly config: Config;
14
+ /** The other projects this sandbox boots with (each one's own settings). */
15
+ readonly others: readonly BootWant[];
16
+ /** The toolchains on for this boot. */
17
+ readonly on: ReadonlySet<UsedBy>;
18
+ readonly storageRoot: string;
19
+ readonly dataPath: string;
20
+ readonly launchId: string;
21
+ /** Whether a sandbox still runs with that data set (a lease held there is not taken over). */
22
+ readonly isRunning: (dataSet: string) => boolean;
23
+ readonly store?: ServiceAccountStore;
24
+ readonly leases?: AccountLeases;
25
+ }
26
+ export declare function prepareServiceAccounts(i: DeliveryInput): AccountDelivery;
@@ -0,0 +1,80 @@
1
+ import { fingerprint, SERVICE_KINDS, SERVICE_SPECS, serviceWanted, } from '../../shared/accounts/index.js';
2
+ import { setNameOf } from '../storage/index.js';
3
+ import { takeHandoffs } from './handoff.js';
4
+ import { AccountLeases } from './leases.js';
5
+ import { ServiceAccountStore } from './store.js';
6
+ /** How to get a first sign-in of a kind, for the note when none is saved. */
7
+ function howToSave(spec) {
8
+ if (spec.kind === 'epic')
9
+ return "sign in to the Epic Games Launcher in this sandbox once, or take this PC's: sandbox account import epic";
10
+ if (spec.kind === 'steam')
11
+ return 'sign in once in this sandbox with sbx-steam login';
12
+ if (spec.returns)
13
+ return 'sign in once in this sandbox, or: sandbox account import ' + spec.kind;
14
+ return spec.sources.includes('typed')
15
+ ? 'save one: sandbox account login ' + spec.kind
16
+ : 'save one: sandbox account import ' + spec.kind + ' --from <file>';
17
+ }
18
+ export function prepareServiceAccounts(i) {
19
+ const store = i.store ?? new ServiceAccountStore(i.storageRoot);
20
+ const leases = i.leases ?? new AccountLeases(i.storageRoot);
21
+ const dataSet = setNameOf(i.dataPath);
22
+ const payloads = {};
23
+ const leased = [];
24
+ const notes = [];
25
+ // A sign-in the last sandbox renewed but did not get to hand over (it did not stop cleanly) comes first.
26
+ try {
27
+ takeHandoffs(i.dataPath, false, store, leases);
28
+ }
29
+ catch {
30
+ // Never stops a launch: the saved sign-in stays as it was.
31
+ }
32
+ for (const kind of SERVICE_KINDS) {
33
+ const spec = SERVICE_SPECS[kind];
34
+ if (!i.config.accounts[kind].share || !serviceWanted(kind, i.on))
35
+ continue;
36
+ const off = i.others.find((o) => !o.config.accounts[kind].share);
37
+ if (off) {
38
+ notes.push('Your ' + spec.label + ' sign-in was not handed over: ' + off.name + ' (also in this sandbox) turns it off.');
39
+ continue;
40
+ }
41
+ try {
42
+ let payload = null;
43
+ let unreadable = false;
44
+ try {
45
+ payload = store.read(kind);
46
+ }
47
+ catch {
48
+ unreadable = true;
49
+ }
50
+ if (spec.singleHolder) {
51
+ const claim = leases.claim(kind, dataSet, i.launchId, payload ? fingerprint(payload) : null, i.isRunning);
52
+ if (!claim.ok) {
53
+ notes.push('Your ' + spec.label + ' sign-in is in use by sandbox ' + claim.heldBy + ': this one starts without it.');
54
+ continue;
55
+ }
56
+ if (spec.returns)
57
+ leased.push(kind);
58
+ }
59
+ if (unreadable) {
60
+ notes.push('Your saved ' +
61
+ spec.label +
62
+ ' sign-in could not be read (only the Windows user that saved it can): ' +
63
+ howToSave(spec) +
64
+ '.');
65
+ continue;
66
+ }
67
+ if (!payload) {
68
+ notes.push('No ' + spec.label + ' sign-in is saved yet: ' + howToSave(spec) + '.');
69
+ continue;
70
+ }
71
+ payloads[kind] = payload;
72
+ notes.push('Your ' + spec.label + ' sign-in is handed to this sandbox: ' + spec.warnings[0]);
73
+ }
74
+ catch {
75
+ // A store or lease file that cannot be written: this kind is left out, the launch goes on.
76
+ notes.push('Your ' + spec.label + ' sign-in could not be prepared, so it was not handed over.');
77
+ }
78
+ }
79
+ return { payloads: payloads, leased, notes };
80
+ }
@@ -0,0 +1,17 @@
1
+ import { type ServiceKind } from '../../shared/accounts/index.js';
2
+ import { AccountLeases } from './leases.js';
3
+ import { ServiceAccountStore } from './store.js';
4
+ /** state\accounts in a data set, as this PC sees it. */
5
+ export declare function handoffDir(dataPath: string): string;
6
+ export declare function takeHandoffs(dataPath: string, final: boolean, store?: ServiceAccountStore, leases?: AccountLeases): {
7
+ taken: ServiceKind[];
8
+ dropped: ServiceKind[];
9
+ };
10
+ /**
11
+ * Forgets a sign-in everywhere: the saved one, its lease (what a running sandbox hands back afterwards is refused),
12
+ * and every copy a sandbox left in a data set and the PC has not taken yet.
13
+ */
14
+ export declare function forgetEverywhere(kind: ServiceKind, storageRoot: string, store?: ServiceAccountStore, leases?: AccountLeases): {
15
+ removed: boolean;
16
+ pendingDeleted: number;
17
+ };