@bitmagic/cli 0.1.83-dev.1 → 0.1.83-dev.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.
Files changed (158) hide show
  1. package/README.md +69 -0
  2. package/assets/skills/publishing-to-steam/SKILL.md +218 -0
  3. package/dist/assets/vxl-summary.d.ts +6 -20
  4. package/dist/assets/vxl-summary.js +9 -100
  5. package/dist/assets/vxl-summary.js.map +1 -1
  6. package/dist/cli.d.ts +1 -0
  7. package/dist/cli.js +2 -0
  8. package/dist/cli.js.map +1 -1
  9. package/dist/commands/check.js +5 -0
  10. package/dist/commands/check.js.map +1 -1
  11. package/dist/commands/forge.js +1 -1
  12. package/dist/commands/forge.js.map +1 -1
  13. package/dist/commands/ios.js +88 -136
  14. package/dist/commands/ios.js.map +1 -1
  15. package/dist/commands/steam-build.d.ts +67 -0
  16. package/dist/commands/steam-build.js +286 -0
  17. package/dist/commands/steam-build.js.map +1 -0
  18. package/dist/commands/steam-portal.d.ts +1 -0
  19. package/dist/commands/steam-portal.js +319 -0
  20. package/dist/commands/steam-portal.js.map +1 -0
  21. package/dist/commands/steam-upload.d.ts +37 -0
  22. package/dist/commands/steam-upload.js +172 -0
  23. package/dist/commands/steam-upload.js.map +1 -0
  24. package/dist/commands/steam.d.ts +1 -0
  25. package/dist/commands/steam.js +502 -0
  26. package/dist/commands/steam.js.map +1 -0
  27. package/dist/commands/verify.js +6 -0
  28. package/dist/commands/verify.js.map +1 -1
  29. package/dist/editor/asset-viewer.js +5 -4
  30. package/dist/editor/asset-viewer.js.map +1 -1
  31. package/dist/editor/server.js +1 -1
  32. package/dist/editor/server.js.map +1 -1
  33. package/dist/editor/shell-page.js +3 -3
  34. package/dist/forge/browser-host.d.ts +1 -1
  35. package/dist/ios/native-overrides.d.ts +27 -0
  36. package/dist/ios/native-overrides.js +87 -0
  37. package/dist/ios/native-overrides.js.map +1 -0
  38. package/dist/ios/record-validate.d.ts +1 -33
  39. package/dist/ios/record-validate.js +2 -100
  40. package/dist/ios/record-validate.js.map +1 -1
  41. package/dist/ios/store-bridge.d.ts +26 -0
  42. package/dist/ios/store-bridge.js +224 -0
  43. package/dist/ios/store-bridge.js.map +1 -0
  44. package/dist/ios/storekit-config.d.ts +15 -0
  45. package/dist/ios/storekit-config.js +106 -0
  46. package/dist/ios/storekit-config.js.map +1 -0
  47. package/dist/ios/xcode-template.d.ts +12 -3
  48. package/dist/ios/xcode-template.js +158 -27
  49. package/dist/ios/xcode-template.js.map +1 -1
  50. package/dist/package/collect-urls.d.ts +1 -1
  51. package/dist/package/collect-urls.js +10 -2
  52. package/dist/package/collect-urls.js.map +1 -1
  53. package/dist/package/offline-payload.d.ts +35 -0
  54. package/dist/package/offline-payload.js +142 -0
  55. package/dist/package/offline-payload.js.map +1 -0
  56. package/dist/package/packaged-check.d.ts +39 -0
  57. package/dist/package/packaged-check.js +104 -0
  58. package/dist/package/packaged-check.js.map +1 -0
  59. package/dist/package/transform.d.ts +4 -4
  60. package/dist/package/transform.js +3 -3
  61. package/dist/project/published-three.d.ts +14 -0
  62. package/dist/project/published-three.js +93 -0
  63. package/dist/project/published-three.js.map +1 -0
  64. package/dist/publish/bundle.js +6 -0
  65. package/dist/publish/bundle.js.map +1 -1
  66. package/dist/scaffold/project-files.js +5 -2
  67. package/dist/scaffold/project-files.js.map +1 -1
  68. package/dist/scaffold/project.js +2 -0
  69. package/dist/scaffold/project.js.map +1 -1
  70. package/dist/scaffold/skill-files.js +3 -1
  71. package/dist/scaffold/skill-files.js.map +1 -1
  72. package/dist/scaffold/steam-skill-files.d.ts +2 -0
  73. package/dist/scaffold/steam-skill-files.js +7 -0
  74. package/dist/scaffold/steam-skill-files.js.map +1 -0
  75. package/dist/steam/art-generate.d.ts +55 -0
  76. package/dist/steam/art-generate.js +173 -0
  77. package/dist/steam/art-generate.js.map +1 -0
  78. package/dist/steam/art-render.d.ts +25 -0
  79. package/dist/steam/art-render.js +157 -0
  80. package/dist/steam/art-render.js.map +1 -0
  81. package/dist/steam/art-use.d.ts +20 -0
  82. package/dist/steam/art-use.js +84 -0
  83. package/dist/steam/art-use.js.map +1 -0
  84. package/dist/steam/art.d.ts +98 -0
  85. package/dist/steam/art.js +243 -0
  86. package/dist/steam/art.js.map +1 -0
  87. package/dist/steam/check.d.ts +21 -0
  88. package/dist/steam/check.js +113 -0
  89. package/dist/steam/check.js.map +1 -0
  90. package/dist/steam/config.d.ts +57 -0
  91. package/dist/steam/config.js +145 -0
  92. package/dist/steam/config.js.map +1 -0
  93. package/dist/steam/electron-cache.d.ts +11 -0
  94. package/dist/steam/electron-cache.js +105 -0
  95. package/dist/steam/electron-cache.js.map +1 -0
  96. package/dist/steam/fuses.d.ts +16 -0
  97. package/dist/steam/fuses.js +75 -0
  98. package/dist/steam/fuses.js.map +1 -0
  99. package/dist/steam/icon-files.d.ts +16 -0
  100. package/dist/steam/icon-files.js +70 -0
  101. package/dist/steam/icon-files.js.map +1 -0
  102. package/dist/steam/image-info.d.ts +16 -0
  103. package/dist/steam/image-info.js +89 -0
  104. package/dist/steam/image-info.js.map +1 -0
  105. package/dist/steam/launch-check.d.ts +12 -0
  106. package/dist/steam/launch-check.js +101 -0
  107. package/dist/steam/launch-check.js.map +1 -0
  108. package/dist/steam/media.d.ts +48 -0
  109. package/dist/steam/media.js +151 -0
  110. package/dist/steam/media.js.map +1 -0
  111. package/dist/steam/package-macos.d.ts +52 -0
  112. package/dist/steam/package-macos.js +268 -0
  113. package/dist/steam/package-macos.js.map +1 -0
  114. package/dist/steam/package-windows.d.ts +24 -0
  115. package/dist/steam/package-windows.js +84 -0
  116. package/dist/steam/package-windows.js.map +1 -0
  117. package/dist/steam/paths.d.ts +37 -0
  118. package/dist/steam/paths.js +45 -0
  119. package/dist/steam/paths.js.map +1 -0
  120. package/dist/steam/playtest-shots.d.ts +18 -0
  121. package/dist/steam/playtest-shots.js +83 -0
  122. package/dist/steam/playtest-shots.js.map +1 -0
  123. package/dist/steam/portal-recipes.d.ts +25 -0
  124. package/dist/steam/portal-recipes.js +61 -0
  125. package/dist/steam/portal-recipes.js.map +1 -0
  126. package/dist/steam/portal.d.ts +58 -0
  127. package/dist/steam/portal.js +340 -0
  128. package/dist/steam/portal.js.map +1 -0
  129. package/dist/steam/raster.d.ts +59 -0
  130. package/dist/steam/raster.js +285 -0
  131. package/dist/steam/raster.js.map +1 -0
  132. package/dist/steam/shell-files.d.ts +17 -0
  133. package/dist/steam/shell-files.js +268 -0
  134. package/dist/steam/shell-files.js.map +1 -0
  135. package/dist/steam/specs.d.ts +61 -0
  136. package/dist/steam/specs.js +103 -0
  137. package/dist/steam/specs.js.map +1 -0
  138. package/dist/steam/steampipe.d.ts +46 -0
  139. package/dist/steam/steampipe.js +170 -0
  140. package/dist/steam/steampipe.js.map +1 -0
  141. package/dist/steam/store-copy.d.ts +89 -0
  142. package/dist/steam/store-copy.js +221 -0
  143. package/dist/steam/store-copy.js.map +1 -0
  144. package/dist/steam/tags.d.ts +9 -0
  145. package/dist/steam/tags.js +49 -0
  146. package/dist/steam/tags.js.map +1 -0
  147. package/dist/steam/trailer.d.ts +34 -0
  148. package/dist/steam/trailer.js +141 -0
  149. package/dist/steam/trailer.js.map +1 -0
  150. package/dist/verify/browser.d.ts +11 -0
  151. package/dist/verify/browser.js +195 -152
  152. package/dist/verify/browser.js.map +1 -1
  153. package/dist/verify/classify.d.ts +11 -2
  154. package/dist/verify/classify.js +16 -4
  155. package/dist/verify/classify.js.map +1 -1
  156. package/dist/verify/mobile-parity.d.ts +1 -1
  157. package/dist/verify/mobile-parity.js +1 -1
  158. package/package.json +5 -4
@@ -0,0 +1,105 @@
1
+ /**
2
+ * The prebuilt Electron releases the desktop shells are stamped from — downloaded once per machine
3
+ * into `~/.bitmagic/cache/electron/v<version>/`, checked against the release's SHASUMS256.txt, and
4
+ * extracted with the symlinks and executable bits an .app needs.
5
+ *
6
+ * The version is pinned in one constant. It IS the Chromium the game ships with, so bumps are
7
+ * deliberate (retest WebGPU, WebGL and fullscreen on both platforms) but should not lag: Electron
8
+ * supports its three newest majors, and an unsupported one stops getting Chromium's security fixes.
9
+ */
10
+ import { spawnSync } from 'child_process';
11
+ import { createHash } from 'crypto';
12
+ import * as fs from 'fs';
13
+ import * as os from 'os';
14
+ import * as path from 'path';
15
+ import { unzipSync } from 'fflate';
16
+ import { CliError } from '../errors.js';
17
+ export const ELECTRON_VERSION = '44.5.1';
18
+ const DOWNLOAD_BASE = process.env.ELECTRON_MIRROR ?? 'https://github.com/electron/electron/releases/download';
19
+ export function electronCacheDir(baseDir = path.join(os.homedir(), '.bitmagic')) {
20
+ return path.join(baseDir, 'cache', 'electron', `v${ELECTRON_VERSION}`);
21
+ }
22
+ async function download(url) {
23
+ let response;
24
+ try {
25
+ response = await fetch(url, { redirect: 'follow', signal: AbortSignal.timeout(15 * 60_000) });
26
+ }
27
+ catch (error) {
28
+ throw new CliError(`Could not download ${url}: ${error instanceof Error ? error.message : String(error)}`);
29
+ }
30
+ if (!response.ok)
31
+ throw new CliError(`Could not download ${url} (HTTP ${response.status}).`);
32
+ return Buffer.from(await response.arrayBuffer());
33
+ }
34
+ /** `SHASUMS256.txt` → file name → sha256. Pure. */
35
+ export function parseShaSums(text) {
36
+ const sums = new Map();
37
+ for (const line of text.split('\n')) {
38
+ const match = /^([0-9a-f]{64})\s+\*?(.+)$/.exec(line.trim());
39
+ if (match)
40
+ sums.set(match[2].trim(), match[1]);
41
+ }
42
+ return sums;
43
+ }
44
+ /** Write every entry of a zip with no symlinks in it (the Windows release). */
45
+ function extractPlainZip(zip, dest) {
46
+ const files = unzipSync(new Uint8Array(zip));
47
+ for (const [name, data] of Object.entries(files)) {
48
+ const target = path.join(dest, ...name.split('/'));
49
+ if (!target.startsWith(dest + path.sep))
50
+ throw new CliError(`Refusing a zip entry outside the target: ${name}`);
51
+ if (name.endsWith('/')) {
52
+ fs.mkdirSync(target, { recursive: true });
53
+ continue;
54
+ }
55
+ fs.mkdirSync(path.dirname(target), { recursive: true });
56
+ fs.writeFileSync(target, data);
57
+ }
58
+ }
59
+ /**
60
+ * The extracted release for `platform`, downloading and verifying it first if needed. macOS
61
+ * releases are unpacked with `ditto`, the only extractor that keeps a framework's symlinks as
62
+ * symlinks — which is also why the macOS build needs a Mac.
63
+ */
64
+ export async function ensureElectronShell(platform, log) {
65
+ const root = electronCacheDir();
66
+ const extracted = path.join(root, platform);
67
+ const marker = path.join(extracted, '.extracted');
68
+ if (fs.existsSync(marker))
69
+ return extracted;
70
+ fs.mkdirSync(root, { recursive: true });
71
+ const zipName = `electron-v${ELECTRON_VERSION}-${platform}.zip`;
72
+ const zipPath = path.join(root, zipName);
73
+ if (!fs.existsSync(zipPath)) {
74
+ const shaPath = path.join(root, 'SHASUMS256.txt');
75
+ if (!fs.existsSync(shaPath))
76
+ fs.writeFileSync(shaPath, await download(`${DOWNLOAD_BASE}/v${ELECTRON_VERSION}/SHASUMS256.txt`));
77
+ const expected = parseShaSums(fs.readFileSync(shaPath, 'utf-8')).get(zipName);
78
+ if (expected === undefined)
79
+ throw new CliError(`Electron's SHASUMS256.txt has no entry for ${zipName}.`);
80
+ log(`Downloading Electron ${ELECTRON_VERSION} for ${platform} (about 100 MB, once per machine)…`);
81
+ const bytes = await download(`${DOWNLOAD_BASE}/v${ELECTRON_VERSION}/${zipName}`);
82
+ const actual = createHash('sha256').update(bytes).digest('hex');
83
+ if (actual !== expected)
84
+ throw new CliError(`${zipName} failed its checksum (expected ${expected}, got ${actual}). Nothing was used.`);
85
+ const tmp = `${zipPath}.tmp-${process.pid}`;
86
+ fs.writeFileSync(tmp, bytes);
87
+ fs.renameSync(tmp, zipPath);
88
+ }
89
+ fs.rmSync(extracted, { recursive: true, force: true });
90
+ fs.mkdirSync(extracted, { recursive: true });
91
+ if (platform.startsWith('darwin')) {
92
+ if (process.platform !== 'darwin') {
93
+ throw new CliError('The macOS build has to be made on a Mac: its app bundle, signing and notarization all need macOS tools.', 3);
94
+ }
95
+ const result = spawnSync('ditto', ['-x', '-k', zipPath, extracted], { stdio: ['ignore', 'ignore', 'pipe'] });
96
+ if (result.status !== 0)
97
+ throw new CliError(`ditto could not extract ${zipName}: ${result.stderr?.toString() ?? ''}`);
98
+ }
99
+ else {
100
+ extractPlainZip(fs.readFileSync(zipPath), extracted);
101
+ }
102
+ fs.writeFileSync(marker, new Date().toISOString());
103
+ return extracted;
104
+ }
105
+ //# sourceMappingURL=electron-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"electron-cache.js","sourceRoot":"","sources":["../../src/steam/electron-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /** Fuse positions on wire version 1. */
2
+ export declare const FUSE: {
3
+ readonly RunAsNode: 0;
4
+ readonly EnableCookieEncryption: 1;
5
+ readonly EnableNodeOptionsEnvironmentVariable: 2;
6
+ readonly EnableNodeCliInspectArguments: 3;
7
+ readonly EnableEmbeddedAsarIntegrityValidation: 4;
8
+ readonly OnlyLoadAppFromAsar: 5;
9
+ };
10
+ /** What a shipped game turns off: running as Node, NODE_OPTIONS, and --inspect. */
11
+ export declare const GAME_FUSES_OFF: readonly number[];
12
+ /** Flip `off` fuses to '0' in place, in every wire found. Returns how many wires were patched. Pure. */
13
+ export declare function flipFuses(binary: Buffer, off: readonly number[]): number;
14
+ /** Read the fuse states of the first wire, for checks and tests. Pure. */
15
+ export declare function readFuses(binary: Buffer): string | null;
16
+ export declare function flipFusesInFile(file: string, off?: readonly number[]): number;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Electron fuses: switches compiled into the Electron binary that turn whole features off before
3
+ * any code runs. A shipped game needs none of the developer features they guard, and with them on
4
+ * the signed, notarized game binary is a general-purpose Node.js runtime anyone can borrow
5
+ * (`ELECTRON_RUN_AS_NODE=1 "Game.app/..." their-script.js` runs with the game's signature and
6
+ * permissions). So the three that make that possible are flipped off at build time, before signing.
7
+ *
8
+ * The fuse wire is a fixed sentinel followed by a version byte, a length byte and one ASCII byte
9
+ * per fuse ('0' off, '1' on, 'r' removed). A universal macOS binary carries one wire per
10
+ * architecture; every occurrence is flipped. The layout is Electron's own (@electron/fuses).
11
+ */
12
+ import * as fs from 'fs';
13
+ import { CliError } from '../errors.js';
14
+ const SENTINEL = Buffer.from('dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX', 'ascii');
15
+ const SUPPORTED_WIRE_VERSION = 1;
16
+ /** Fuse positions on wire version 1. */
17
+ export const FUSE = {
18
+ RunAsNode: 0,
19
+ EnableCookieEncryption: 1,
20
+ EnableNodeOptionsEnvironmentVariable: 2,
21
+ EnableNodeCliInspectArguments: 3,
22
+ EnableEmbeddedAsarIntegrityValidation: 4,
23
+ OnlyLoadAppFromAsar: 5,
24
+ };
25
+ /** What a shipped game turns off: running as Node, NODE_OPTIONS, and --inspect. */
26
+ export const GAME_FUSES_OFF = [
27
+ FUSE.RunAsNode,
28
+ FUSE.EnableNodeOptionsEnvironmentVariable,
29
+ FUSE.EnableNodeCliInspectArguments,
30
+ ];
31
+ /** Flip `off` fuses to '0' in place, in every wire found. Returns how many wires were patched. Pure. */
32
+ export function flipFuses(binary, off) {
33
+ let wires = 0;
34
+ let from = 0;
35
+ for (;;) {
36
+ const at = binary.indexOf(SENTINEL, from);
37
+ if (at < 0)
38
+ break;
39
+ const versionAt = at + SENTINEL.length;
40
+ const version = binary[versionAt];
41
+ const length = binary[versionAt + 1];
42
+ if (version !== SUPPORTED_WIRE_VERSION) {
43
+ throw new CliError(`This Electron's fuse wire is version ${version}; the CLI understands version ${SUPPORTED_WIRE_VERSION}.`);
44
+ }
45
+ for (const index of off) {
46
+ if (index >= length)
47
+ continue;
48
+ const byteAt = versionAt + 2 + index;
49
+ // 'r' means removed from this Electron — nothing to flip.
50
+ if (binary[byteAt] === 0x30 || binary[byteAt] === 0x31)
51
+ binary[byteAt] = 0x30;
52
+ }
53
+ wires += 1;
54
+ from = versionAt + 2 + length;
55
+ }
56
+ return wires;
57
+ }
58
+ /** Read the fuse states of the first wire, for checks and tests. Pure. */
59
+ export function readFuses(binary) {
60
+ const at = binary.indexOf(SENTINEL);
61
+ if (at < 0)
62
+ return null;
63
+ const versionAt = at + SENTINEL.length;
64
+ const length = binary[versionAt + 1];
65
+ return binary.toString('ascii', versionAt + 2, versionAt + 2 + length);
66
+ }
67
+ export function flipFusesInFile(file, off = GAME_FUSES_OFF) {
68
+ const binary = fs.readFileSync(file);
69
+ const wires = flipFuses(binary, off);
70
+ if (wires === 0)
71
+ throw new CliError(`No Electron fuse wire in ${file} — is this an Electron binary?`);
72
+ fs.writeFileSync(file, binary);
73
+ return wires;
74
+ }
75
+ //# sourceMappingURL=fuses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fuses.js","sourceRoot":"","sources":["../../src/steam/fuses.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Windows .ico and macOS .icns containers around PNG images.
3
+ *
4
+ * Both formats are a small directory in front of PNG payloads, which every Windows since Vista and
5
+ * every macOS since 10.7 read directly, so writing them is a few dozen lines and needs no image
6
+ * library — the PNGs themselves come from the raster browser (raster.ts).
7
+ */
8
+ /** One square PNG and its edge length in pixels. */
9
+ export interface SizedPng {
10
+ size: number;
11
+ png: Buffer;
12
+ }
13
+ /** An .ico holding each PNG as its own entry. Sizes above 256 are not representable and are skipped. */
14
+ export declare function buildIco(images: readonly SizedPng[]): Buffer;
15
+ /** An .icns with every slot whose size `images` provides. */
16
+ export declare function buildIcns(images: readonly SizedPng[]): Buffer;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Windows .ico and macOS .icns containers around PNG images.
3
+ *
4
+ * Both formats are a small directory in front of PNG payloads, which every Windows since Vista and
5
+ * every macOS since 10.7 read directly, so writing them is a few dozen lines and needs no image
6
+ * library — the PNGs themselves come from the raster browser (raster.ts).
7
+ */
8
+ /** An .ico holding each PNG as its own entry. Sizes above 256 are not representable and are skipped. */
9
+ export function buildIco(images) {
10
+ const entries = images.filter((image) => image.size >= 1 && image.size <= 256)
11
+ .sort((a, b) => a.size - b.size);
12
+ const header = Buffer.alloc(6);
13
+ header.writeUInt16LE(0, 0);
14
+ header.writeUInt16LE(1, 2);
15
+ header.writeUInt16LE(entries.length, 4);
16
+ const directory = Buffer.alloc(16 * entries.length);
17
+ let offset = header.length + directory.length;
18
+ entries.forEach((entry, index) => {
19
+ const at = index * 16;
20
+ // 256 is stored as 0: the field is one byte.
21
+ directory[at] = entry.size === 256 ? 0 : entry.size;
22
+ directory[at + 1] = entry.size === 256 ? 0 : entry.size;
23
+ directory[at + 2] = 0;
24
+ directory[at + 3] = 0;
25
+ directory.writeUInt16LE(1, at + 4);
26
+ directory.writeUInt16LE(32, at + 6);
27
+ directory.writeUInt32LE(entry.png.length, at + 8);
28
+ directory.writeUInt32LE(offset, at + 12);
29
+ offset += entry.png.length;
30
+ });
31
+ return Buffer.concat([header, directory, ...entries.map((entry) => entry.png)]);
32
+ }
33
+ /**
34
+ * The icns element types that carry PNG data, by pixel size. Retina slots (`ic11`..`ic14`) hold the
35
+ * same pixels as the next size up, labelled as the smaller point size at 2x, so the Finder and the
36
+ * Dock pick a sharp image at every scale.
37
+ */
38
+ const ICNS_TYPES = [
39
+ ['icp4', 16],
40
+ ['icp5', 32],
41
+ ['ic11', 32],
42
+ ['icp6', 64],
43
+ ['ic12', 64],
44
+ ['ic07', 128],
45
+ ['ic08', 256],
46
+ ['ic13', 256],
47
+ ['ic09', 512],
48
+ ['ic14', 512],
49
+ ['ic10', 1024],
50
+ ];
51
+ /** An .icns with every slot whose size `images` provides. */
52
+ export function buildIcns(images) {
53
+ const bySize = new Map(images.map((image) => [image.size, image.png]));
54
+ const elements = [];
55
+ for (const [type, size] of ICNS_TYPES) {
56
+ const png = bySize.get(size);
57
+ if (png === undefined)
58
+ continue;
59
+ const head = Buffer.alloc(8);
60
+ head.write(type, 0, 'ascii');
61
+ head.writeUInt32BE(png.length + 8, 4);
62
+ elements.push(head, png);
63
+ }
64
+ const body = Buffer.concat(elements);
65
+ const header = Buffer.alloc(8);
66
+ header.write('icns', 0, 'ascii');
67
+ header.writeUInt32BE(body.length + 8, 4);
68
+ return Buffer.concat([header, body]);
69
+ }
70
+ //# sourceMappingURL=icon-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icon-files.js","sourceRoot":"","sources":["../../src/steam/icon-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Read an image's format, size and whether it can be transparent — from the header bytes, with no
3
+ * decoder. The check needs exactly these three facts about every file in the store kit, and the
4
+ * CLI deliberately carries no native image library.
5
+ */
6
+ export interface ImageInfo {
7
+ format: 'png' | 'jpg' | 'webp';
8
+ width: number;
9
+ height: number;
10
+ /** The format and header allow transparency (PNG colour type 4/6 or a tRNS chunk; WebP alpha). */
11
+ alpha: boolean;
12
+ }
13
+ /** The image's header facts, or null for bytes that are none of PNG, JPEG or WebP. */
14
+ export declare function readImageInfo(bytes: Buffer): ImageInfo | null;
15
+ /** `image/png` etc., for a data URL. */
16
+ export declare function mimeForImage(info: ImageInfo): string;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Read an image's format, size and whether it can be transparent — from the header bytes, with no
3
+ * decoder. The check needs exactly these three facts about every file in the store kit, and the
4
+ * CLI deliberately carries no native image library.
5
+ */
6
+ const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
7
+ function readPng(bytes) {
8
+ if (bytes.length < 33 || !bytes.subarray(0, 8).equals(PNG_SIGNATURE))
9
+ return null;
10
+ if (bytes.toString('ascii', 12, 16) !== 'IHDR')
11
+ return null;
12
+ const width = bytes.readUInt32BE(16);
13
+ const height = bytes.readUInt32BE(20);
14
+ const colourType = bytes[25];
15
+ let alpha = colourType === 4 || colourType === 6;
16
+ // A palette or opaque-colour PNG can still be transparent through a tRNS chunk, which sits
17
+ // between IHDR and the first IDAT.
18
+ let offset = 8;
19
+ while (!alpha && offset + 8 <= bytes.length) {
20
+ const length = bytes.readUInt32BE(offset);
21
+ const type = bytes.toString('ascii', offset + 4, offset + 8);
22
+ if (type === 'tRNS')
23
+ alpha = true;
24
+ if (type === 'IDAT' || type === 'IEND')
25
+ break;
26
+ offset += 12 + length;
27
+ }
28
+ return { format: 'png', width, height, alpha };
29
+ }
30
+ /** Start-of-frame markers carry the size; DHT (C4), JPG (C8) and DAC (CC) share the range but do not. */
31
+ function isStartOfFrame(marker) {
32
+ return marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
33
+ }
34
+ function readJpeg(bytes) {
35
+ if (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8)
36
+ return null;
37
+ let offset = 2;
38
+ while (offset + 4 <= bytes.length) {
39
+ if (bytes[offset] !== 0xff)
40
+ return null;
41
+ const marker = bytes[offset + 1];
42
+ // Fill bytes and standalone markers have no length field.
43
+ if (marker === 0xff) {
44
+ offset += 1;
45
+ continue;
46
+ }
47
+ if (marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd7) || marker === 0x01) {
48
+ offset += 2;
49
+ continue;
50
+ }
51
+ const length = bytes.readUInt16BE(offset + 2);
52
+ if (isStartOfFrame(marker) && offset + 9 <= bytes.length) {
53
+ return { format: 'jpg', height: bytes.readUInt16BE(offset + 5), width: bytes.readUInt16BE(offset + 7), alpha: false };
54
+ }
55
+ offset += 2 + length;
56
+ }
57
+ return null;
58
+ }
59
+ function readWebp(bytes) {
60
+ if (bytes.length < 30 || bytes.toString('ascii', 0, 4) !== 'RIFF' || bytes.toString('ascii', 8, 12) !== 'WEBP') {
61
+ return null;
62
+ }
63
+ const chunk = bytes.toString('ascii', 12, 16);
64
+ if (chunk === 'VP8X') {
65
+ const flags = bytes[20];
66
+ const width = 1 + bytes.readUIntLE(24, 3);
67
+ const height = 1 + bytes.readUIntLE(27, 3);
68
+ return { format: 'webp', width, height, alpha: (flags & 0x10) !== 0 };
69
+ }
70
+ if (chunk === 'VP8L') {
71
+ const b0 = bytes[21], b1 = bytes[22], b2 = bytes[23], b3 = bytes[24];
72
+ const width = 1 + (((b1 & 0x3f) << 8) | b0);
73
+ const height = 1 + (((b3 & 0x0f) << 10) | (b2 << 2) | ((b1 & 0xc0) >> 6));
74
+ return { format: 'webp', width, height, alpha: (b3 & 0x10) !== 0 };
75
+ }
76
+ if (chunk === 'VP8 ') {
77
+ return { format: 'webp', width: bytes.readUInt16LE(26) & 0x3fff, height: bytes.readUInt16LE(28) & 0x3fff, alpha: false };
78
+ }
79
+ return null;
80
+ }
81
+ /** The image's header facts, or null for bytes that are none of PNG, JPEG or WebP. */
82
+ export function readImageInfo(bytes) {
83
+ return readPng(bytes) ?? readJpeg(bytes) ?? readWebp(bytes);
84
+ }
85
+ /** `image/png` etc., for a data URL. */
86
+ export function mimeForImage(info) {
87
+ return info.format === 'jpg' ? 'image/jpeg' : `image/${info.format}`;
88
+ }
89
+ //# sourceMappingURL=image-info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-info.js","sourceRoot":"","sources":["../../src/steam/image-info.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { type VerifyResult } from '../verify/classify.js';
2
+ export interface LaunchCheckResult {
3
+ result: VerifyResult;
4
+ screenshotPath: string;
5
+ consolePath: string;
6
+ }
7
+ export declare function launchCheckApp(options: {
8
+ /** The executable inside the app: `<App>.app/Contents/MacOS/<Product>` or `<Game>.exe`. */
9
+ executable: string;
10
+ artifactDir: string;
11
+ settleMs?: number;
12
+ }): Promise<LaunchCheckResult>;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Launch the finished desktop app — the real Electron binary, signed and fused as it ships — and
3
+ * hold it to the same verdict `bitmagic verify` gives a browser tab: the game starts, renders and
4
+ * reaches gameplay. The payload was already proven offline in headless Chrome; this run proves the
5
+ * shell around it: the bitmagic:// protocol, the MIME types, the window, Electron's GPU process.
6
+ *
7
+ * Only the build machine's own platform can be launched, so a Mac checks the macOS app and the
8
+ * Windows build is checked for structure only.
9
+ */
10
+ import { spawn } from 'child_process';
11
+ import * as fs from 'fs';
12
+ import * as net from 'net';
13
+ import * as os from 'os';
14
+ import * as path from 'path';
15
+ import { chromium } from 'playwright-core';
16
+ import { CliError } from '../errors.js';
17
+ import { observeGamePage } from '../verify/browser.js';
18
+ import { classifyVerifyRun } from '../verify/classify.js';
19
+ import { DEFAULT_START_INTERACTION } from '../verify/start.js';
20
+ import { SHELL_HOST, SHELL_SCHEME } from './shell-files.js';
21
+ function freePort() {
22
+ return new Promise((resolve, reject) => {
23
+ const server = net.createServer();
24
+ server.once('error', reject);
25
+ server.listen(0, '127.0.0.1', () => {
26
+ const address = server.address();
27
+ const port = typeof address === 'object' && address !== null ? address.port : 0;
28
+ server.close(() => resolve(port));
29
+ });
30
+ });
31
+ }
32
+ async function waitForEndpoint(port, timeoutMs) {
33
+ const deadline = Date.now() + timeoutMs;
34
+ while (Date.now() < deadline) {
35
+ try {
36
+ const response = await fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(1000) });
37
+ if (response.ok)
38
+ return;
39
+ }
40
+ catch {
41
+ // Not up yet.
42
+ }
43
+ await new Promise((resolve) => setTimeout(resolve, 300));
44
+ }
45
+ throw new CliError('The packaged app did not open a debugging port — it may have crashed on launch. Run it by hand to see why.', 5);
46
+ }
47
+ export async function launchCheckApp(options) {
48
+ fs.mkdirSync(options.artifactDir, { recursive: true });
49
+ const port = await freePort();
50
+ const profile = fs.mkdtempSync(path.join(os.tmpdir(), 'bitmagic-shell-check-'));
51
+ // --enable-automation is what makes navigator.webdriver true in the browser verify drives;
52
+ // games read it (custom start screens press their own Play for a verify run), so the packaged
53
+ // app has to present the same flag for the same verdict to apply.
54
+ const child = spawn(options.executable, [`--remote-debugging-port=${port}`, '--remote-debugging-address=127.0.0.1', '--enable-automation'], {
55
+ env: { ...process.env, BITMAGIC_SHELL_CHECK: profile },
56
+ stdio: ['ignore', 'ignore', 'pipe'],
57
+ });
58
+ let stderr = '';
59
+ child.stderr?.on('data', (chunk) => { stderr = (stderr + chunk.toString('utf-8')).slice(-8000); });
60
+ const screenshotPath = path.join(options.artifactDir, 'screenshot.png');
61
+ const consolePath = path.join(options.artifactDir, 'console.log');
62
+ try {
63
+ await waitForEndpoint(port, 30_000);
64
+ const browser = await chromium.connectOverCDP(`http://127.0.0.1:${port}`);
65
+ try {
66
+ const origin = `${SHELL_SCHEME}://${SHELL_HOST}/`;
67
+ let page;
68
+ const deadline = Date.now() + 20_000;
69
+ while (page === undefined && Date.now() < deadline) {
70
+ page = browser.contexts().flatMap((context) => context.pages()).find((candidate) => candidate.url().startsWith(origin));
71
+ if (page === undefined)
72
+ await new Promise((resolve) => setTimeout(resolve, 300));
73
+ }
74
+ if (page === undefined)
75
+ throw new CliError(`The packaged app never loaded ${origin}index.html.`, 5);
76
+ // The app loaded its page before anything could attach; reloading runs it again with the
77
+ // verify probes armed from the first script.
78
+ const observations = await observeGamePage(page, {
79
+ url: `${origin}index.html`,
80
+ screenshotPath,
81
+ consolePath,
82
+ settleMs: options.settleMs ?? 25_000,
83
+ startInteraction: DEFAULT_START_INTERACTION,
84
+ }, page.viewportSize() ?? { width: 1280, height: 720 }, (target) => target.reload({ waitUntil: 'load', timeout: 45_000 }));
85
+ return { result: classifyVerifyRun(observations), screenshotPath, consolePath };
86
+ }
87
+ finally {
88
+ await browser.close().catch(() => undefined);
89
+ }
90
+ }
91
+ finally {
92
+ child.kill('SIGTERM');
93
+ await new Promise((resolve) => setTimeout(resolve, 500));
94
+ if (child.exitCode === null)
95
+ child.kill('SIGKILL');
96
+ fs.rmSync(profile, { recursive: true, force: true });
97
+ if (stderr.trim() !== '')
98
+ fs.writeFileSync(path.join(options.artifactDir, 'app-stderr.log'), stderr);
99
+ }
100
+ }
101
+ //# sourceMappingURL=launch-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-check.js","sourceRoot":"","sources":["../../src/steam/launch-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ export interface VideoProbe {
2
+ width: number;
3
+ height: number;
4
+ fps: number;
5
+ durationSec: number;
6
+ /** Whole-file bit rate, kbit/s. */
7
+ bitrateKbps: number;
8
+ videoCodec: string;
9
+ audioCodec: string | null;
10
+ /** ffprobe's format names, e.g. `mov,mp4,m4a,3gp,3g2,mj2`. */
11
+ container: string;
12
+ }
13
+ /** Pure, so the parsing is testable without ffprobe. */
14
+ export declare function parseVideoProbe(json: string, file: string): VideoProbe;
15
+ export declare function probeVideo(file: string): Promise<VideoProbe>;
16
+ /** The times (seconds) at which the picture cuts, from ffmpeg's scene-change score. */
17
+ export declare function detectCuts(file: string, threshold?: number): Promise<number[]>;
18
+ export declare function parseCutTimes(output: string): number[];
19
+ export interface Shot {
20
+ from: number;
21
+ to: number;
22
+ }
23
+ /**
24
+ * Up to `count` shots worth a screenshot: the longest ones, so every pick is a different moment
25
+ * and none sits on a transition. `trimEndSec` skips the tail, where trailers put their end card.
26
+ * Returned in time order. Pure.
27
+ */
28
+ export declare function pickScreenshotShots(options: {
29
+ durationSec: number;
30
+ cuts: readonly number[];
31
+ count: number;
32
+ trimStartSec?: number;
33
+ trimEndSec?: number;
34
+ }): Shot[];
35
+ /** ffmpeg's blurdetect score for the frame at `atSec` — higher is blurrier. */
36
+ export declare function blurScore(file: string, atSec: number): Promise<number>;
37
+ /**
38
+ * The sharpest of `samples` frames across the middle of a shot. Gameplay footage carries motion
39
+ * blur on fast moments, and the middle frame of a shot is as likely as any to be the smeared one;
40
+ * a screenshot is looked at still, so it should be the crispest frame the shot has.
41
+ */
42
+ export declare function sharpestMoment(file: string, shot: Shot, samples?: number): Promise<number>;
43
+ /** One frame at `atSec`, scaled to cover `width`x`height` and centre-cropped to it, as PNG. */
44
+ export declare function extractFrame(file: string, atSec: number, out: string, width: number, height: number): Promise<void>;
45
+ /** Normalise any still to `width`x`height` PNG the same way. */
46
+ export declare function normaliseStill(file: string, out: string, width: number, height: number): Promise<void>;
47
+ /** A grid of the stills, for the agent to look at in one image instead of eight. */
48
+ export declare function contactSheet(files: readonly string[], out: string): Promise<void>;