@bitmagic/cli 0.1.83-dev.0 → 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.
- package/README.md +70 -1
- package/assets/skills/publishing-to-steam/SKILL.md +218 -0
- package/dist/assets/vxl-summary.d.ts +6 -20
- package/dist/assets/vxl-summary.js +9 -100
- package/dist/assets/vxl-summary.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/check.js +5 -0
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/forge.js +1 -1
- package/dist/commands/forge.js.map +1 -1
- package/dist/commands/import.js +2 -4
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.js +0 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/ios.js +88 -136
- package/dist/commands/ios.js.map +1 -1
- package/dist/commands/promote.js +2 -4
- package/dist/commands/promote.js.map +1 -1
- package/dist/commands/publish.js +0 -1
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/steam-build.d.ts +67 -0
- package/dist/commands/steam-build.js +286 -0
- package/dist/commands/steam-build.js.map +1 -0
- package/dist/commands/steam-portal.d.ts +1 -0
- package/dist/commands/steam-portal.js +319 -0
- package/dist/commands/steam-portal.js.map +1 -0
- package/dist/commands/steam-upload.d.ts +37 -0
- package/dist/commands/steam-upload.js +172 -0
- package/dist/commands/steam-upload.js.map +1 -0
- package/dist/commands/steam.d.ts +1 -0
- package/dist/commands/steam.js +502 -0
- package/dist/commands/steam.js.map +1 -0
- package/dist/commands/verify.js +6 -0
- package/dist/commands/verify.js.map +1 -1
- package/dist/editor/asset-viewer.js +5 -4
- package/dist/editor/asset-viewer.js.map +1 -1
- package/dist/editor/game-info-panel.d.ts +2 -2
- package/dist/editor/game-info-panel.js +1 -2
- package/dist/editor/game-info-panel.js.map +1 -1
- package/dist/editor/game-info.d.ts +1 -3
- package/dist/editor/game-info.js +1 -2
- package/dist/editor/game-info.js.map +1 -1
- package/dist/editor/server.js +2 -3
- package/dist/editor/server.js.map +1 -1
- package/dist/editor/shell-page.js +9 -7
- package/dist/editor/shell-page.js.map +1 -1
- package/dist/forge/browser-host.d.ts +6 -12
- package/dist/forge/browser-host.js +5 -11
- package/dist/forge/browser-host.js.map +1 -1
- package/dist/ios/native-overrides.d.ts +27 -0
- package/dist/ios/native-overrides.js +87 -0
- package/dist/ios/native-overrides.js.map +1 -0
- package/dist/ios/record-validate.d.ts +1 -33
- package/dist/ios/record-validate.js +2 -100
- package/dist/ios/record-validate.js.map +1 -1
- package/dist/ios/store-bridge.d.ts +26 -0
- package/dist/ios/store-bridge.js +224 -0
- package/dist/ios/store-bridge.js.map +1 -0
- package/dist/ios/storekit-config.d.ts +15 -0
- package/dist/ios/storekit-config.js +106 -0
- package/dist/ios/storekit-config.js.map +1 -0
- package/dist/ios/xcode-template.d.ts +12 -3
- package/dist/ios/xcode-template.js +158 -27
- package/dist/ios/xcode-template.js.map +1 -1
- package/dist/package/collect-urls.d.ts +1 -1
- package/dist/package/collect-urls.js +10 -2
- package/dist/package/collect-urls.js.map +1 -1
- package/dist/package/offline-payload.d.ts +35 -0
- package/dist/package/offline-payload.js +142 -0
- package/dist/package/offline-payload.js.map +1 -0
- package/dist/package/packaged-check.d.ts +39 -0
- package/dist/package/packaged-check.js +104 -0
- package/dist/package/packaged-check.js.map +1 -0
- package/dist/package/transform.d.ts +4 -4
- package/dist/package/transform.js +3 -3
- package/dist/project/context.d.ts +7 -2
- package/dist/project/context.js +1 -2
- package/dist/project/context.js.map +1 -1
- package/dist/project/published-three.d.ts +14 -0
- package/dist/project/published-three.js +93 -0
- package/dist/project/published-three.js.map +1 -0
- package/dist/publish/bundle.d.ts +1 -1
- package/dist/publish/bundle.js +8 -3
- package/dist/publish/bundle.js.map +1 -1
- package/dist/publish/platform.d.ts +1 -1
- package/dist/publish/platform.js +1 -1
- package/dist/publish/source-zip.d.ts +0 -1
- package/dist/publish/source-zip.js +1 -2
- package/dist/publish/source-zip.js.map +1 -1
- package/dist/scaffold/aliases.js +8 -5
- package/dist/scaffold/aliases.js.map +1 -1
- package/dist/scaffold/engine-download.d.ts +1 -1
- package/dist/scaffold/engine-download.js +1 -1
- package/dist/scaffold/export-zip.d.ts +0 -1
- package/dist/scaffold/export-zip.js +3 -6
- package/dist/scaffold/export-zip.js.map +1 -1
- package/dist/scaffold/import-project.js +1 -1
- package/dist/scaffold/project-export.js +0 -1
- package/dist/scaffold/project-export.js.map +1 -1
- package/dist/scaffold/project-files.d.ts +0 -1
- package/dist/scaffold/project-files.js +8 -5
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/scaffold/project.d.ts +15 -4
- package/dist/scaffold/project.js +30 -13
- package/dist/scaffold/project.js.map +1 -1
- package/dist/scaffold/skill-files.js +3 -1
- package/dist/scaffold/skill-files.js.map +1 -1
- package/dist/scaffold/steam-skill-files.d.ts +2 -0
- package/dist/scaffold/steam-skill-files.js +7 -0
- package/dist/scaffold/steam-skill-files.js.map +1 -0
- package/dist/scaffold/suggest-template.js +0 -1
- package/dist/scaffold/suggest-template.js.map +1 -1
- package/dist/scaffold/unity-skill-files.js +1 -1
- package/dist/steam/art-generate.d.ts +55 -0
- package/dist/steam/art-generate.js +173 -0
- package/dist/steam/art-generate.js.map +1 -0
- package/dist/steam/art-render.d.ts +25 -0
- package/dist/steam/art-render.js +157 -0
- package/dist/steam/art-render.js.map +1 -0
- package/dist/steam/art-use.d.ts +20 -0
- package/dist/steam/art-use.js +84 -0
- package/dist/steam/art-use.js.map +1 -0
- package/dist/steam/art.d.ts +98 -0
- package/dist/steam/art.js +243 -0
- package/dist/steam/art.js.map +1 -0
- package/dist/steam/check.d.ts +21 -0
- package/dist/steam/check.js +113 -0
- package/dist/steam/check.js.map +1 -0
- package/dist/steam/config.d.ts +57 -0
- package/dist/steam/config.js +145 -0
- package/dist/steam/config.js.map +1 -0
- package/dist/steam/electron-cache.d.ts +11 -0
- package/dist/steam/electron-cache.js +105 -0
- package/dist/steam/electron-cache.js.map +1 -0
- package/dist/steam/fuses.d.ts +16 -0
- package/dist/steam/fuses.js +75 -0
- package/dist/steam/fuses.js.map +1 -0
- package/dist/steam/icon-files.d.ts +16 -0
- package/dist/steam/icon-files.js +70 -0
- package/dist/steam/icon-files.js.map +1 -0
- package/dist/steam/image-info.d.ts +16 -0
- package/dist/steam/image-info.js +89 -0
- package/dist/steam/image-info.js.map +1 -0
- package/dist/steam/launch-check.d.ts +12 -0
- package/dist/steam/launch-check.js +101 -0
- package/dist/steam/launch-check.js.map +1 -0
- package/dist/steam/media.d.ts +48 -0
- package/dist/steam/media.js +151 -0
- package/dist/steam/media.js.map +1 -0
- package/dist/steam/package-macos.d.ts +52 -0
- package/dist/steam/package-macos.js +268 -0
- package/dist/steam/package-macos.js.map +1 -0
- package/dist/steam/package-windows.d.ts +24 -0
- package/dist/steam/package-windows.js +84 -0
- package/dist/steam/package-windows.js.map +1 -0
- package/dist/steam/paths.d.ts +37 -0
- package/dist/steam/paths.js +45 -0
- package/dist/steam/paths.js.map +1 -0
- package/dist/steam/playtest-shots.d.ts +18 -0
- package/dist/steam/playtest-shots.js +83 -0
- package/dist/steam/playtest-shots.js.map +1 -0
- package/dist/steam/portal-recipes.d.ts +25 -0
- package/dist/steam/portal-recipes.js +61 -0
- package/dist/steam/portal-recipes.js.map +1 -0
- package/dist/steam/portal.d.ts +58 -0
- package/dist/steam/portal.js +340 -0
- package/dist/steam/portal.js.map +1 -0
- package/dist/steam/raster.d.ts +59 -0
- package/dist/steam/raster.js +285 -0
- package/dist/steam/raster.js.map +1 -0
- package/dist/steam/shell-files.d.ts +17 -0
- package/dist/steam/shell-files.js +268 -0
- package/dist/steam/shell-files.js.map +1 -0
- package/dist/steam/specs.d.ts +61 -0
- package/dist/steam/specs.js +103 -0
- package/dist/steam/specs.js.map +1 -0
- package/dist/steam/steampipe.d.ts +46 -0
- package/dist/steam/steampipe.js +170 -0
- package/dist/steam/steampipe.js.map +1 -0
- package/dist/steam/store-copy.d.ts +89 -0
- package/dist/steam/store-copy.js +221 -0
- package/dist/steam/store-copy.js.map +1 -0
- package/dist/steam/tags.d.ts +9 -0
- package/dist/steam/tags.js +49 -0
- package/dist/steam/tags.js.map +1 -0
- package/dist/steam/trailer.d.ts +34 -0
- package/dist/steam/trailer.js +141 -0
- package/dist/steam/trailer.js.map +1 -0
- package/dist/verify/browser.d.ts +11 -0
- package/dist/verify/browser.js +195 -152
- package/dist/verify/browser.js.map +1 -1
- package/dist/verify/classify.d.ts +11 -2
- package/dist/verify/classify.js +16 -4
- package/dist/verify/classify.js.map +1 -1
- package/dist/verify/mobile-parity.d.ts +1 -1
- package/dist/verify/mobile-parity.js +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { launchSystemChrome } from '../chrome-launch.js';
|
|
2
|
+
import { CliError } from '../errors.js';
|
|
3
|
+
import { readImageInfo, mimeForImage } from './image-info.js';
|
|
4
|
+
export function dataUrlFor(bytes) {
|
|
5
|
+
const info = readImageInfo(bytes);
|
|
6
|
+
if (info === null)
|
|
7
|
+
throw new CliError('Not a PNG, JPEG or WebP image.');
|
|
8
|
+
return `data:${mimeForImage(info)};base64,${bytes.toString('base64')}`;
|
|
9
|
+
}
|
|
10
|
+
function bytesFromDataUrl(dataUrl) {
|
|
11
|
+
const comma = dataUrl.indexOf(',');
|
|
12
|
+
if (comma < 0)
|
|
13
|
+
throw new CliError('The browser returned no image data.');
|
|
14
|
+
return Buffer.from(dataUrl.slice(comma + 1), 'base64');
|
|
15
|
+
}
|
|
16
|
+
export class RasterSession {
|
|
17
|
+
browser;
|
|
18
|
+
page;
|
|
19
|
+
constructor(browser, page) {
|
|
20
|
+
this.browser = browser;
|
|
21
|
+
this.page = page;
|
|
22
|
+
}
|
|
23
|
+
static async open(command) {
|
|
24
|
+
const browser = await launchSystemChrome({ headless: true, args: ['--mute-audio'], command });
|
|
25
|
+
try {
|
|
26
|
+
const context = await browser.newContext({ viewport: { width: 1280, height: 720 }, deviceScaleFactor: 1 });
|
|
27
|
+
const page = await context.newPage();
|
|
28
|
+
return new RasterSession(browser, page);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
await browser.close();
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async close() {
|
|
36
|
+
await this.browser.close();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Lay `html` out at exactly `width`x`height` CSS pixels and capture it. `transparent` keeps the
|
|
40
|
+
* page background clear (the library logo); otherwise the page paints black under the art.
|
|
41
|
+
*/
|
|
42
|
+
async render(html, width, height, options = {}) {
|
|
43
|
+
await this.page.setViewportSize({ width, height });
|
|
44
|
+
await this.page.setContent(html, { waitUntil: 'load' });
|
|
45
|
+
// Web fonts and decoded images both arrive after `load` in the worst case.
|
|
46
|
+
await this.page.evaluate(`Promise.all([
|
|
47
|
+
document.fonts.ready,
|
|
48
|
+
...Array.from(document.images).map((img) => img.decode().catch(() => undefined)),
|
|
49
|
+
]).then(() => true)`);
|
|
50
|
+
const format = options.format ?? 'png';
|
|
51
|
+
return this.page.screenshot({
|
|
52
|
+
type: format,
|
|
53
|
+
clip: { x: 0, y: 0, width, height },
|
|
54
|
+
omitBackground: options.transparent === true,
|
|
55
|
+
...(format === 'jpeg' ? { quality: options.quality ?? 92 } : {}),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** Decode any supported image and re-encode it as PNG, with its size. */
|
|
59
|
+
async toPng(bytes) {
|
|
60
|
+
const result = await this.page.evaluate(async (src) => {
|
|
61
|
+
/* eslint-disable no-undef -- browser callback */
|
|
62
|
+
const img = new Image();
|
|
63
|
+
img.src = src;
|
|
64
|
+
await img.decode();
|
|
65
|
+
const canvas = document.createElement('canvas');
|
|
66
|
+
canvas.width = img.naturalWidth;
|
|
67
|
+
canvas.height = img.naturalHeight;
|
|
68
|
+
const ctx = canvas.getContext('2d');
|
|
69
|
+
if (!ctx)
|
|
70
|
+
throw new Error('no 2d context');
|
|
71
|
+
ctx.drawImage(img, 0, 0);
|
|
72
|
+
return { dataUrl: canvas.toDataURL('image/png'), width: canvas.width, height: canvas.height };
|
|
73
|
+
/* eslint-enable no-undef */
|
|
74
|
+
}, dataUrlFor(bytes));
|
|
75
|
+
return { png: bytesFromDataUrl(result.dataUrl), width: result.width, height: result.height };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Remove a flat background colour and trim to what is left, plus `pad` pixels. Partially
|
|
79
|
+
* keyed edge pixels are un-mixed from the key colour (c = (observed - (1-a)·key) / a), so an
|
|
80
|
+
* anti-aliased outline keeps its own colour instead of a green or magenta fringe.
|
|
81
|
+
*/
|
|
82
|
+
async keyAndTrim(bytes, options) {
|
|
83
|
+
const result = await this.page.evaluate(async (args) => {
|
|
84
|
+
/* eslint-disable no-undef -- browser callback */
|
|
85
|
+
const img = new Image();
|
|
86
|
+
img.src = args.src;
|
|
87
|
+
await img.decode();
|
|
88
|
+
const w = img.naturalWidth;
|
|
89
|
+
const h = img.naturalHeight;
|
|
90
|
+
const canvas = document.createElement('canvas');
|
|
91
|
+
canvas.width = w;
|
|
92
|
+
canvas.height = h;
|
|
93
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
94
|
+
if (!ctx)
|
|
95
|
+
throw new Error('no 2d context');
|
|
96
|
+
ctx.drawImage(img, 0, 0);
|
|
97
|
+
const data = ctx.getImageData(0, 0, w, h);
|
|
98
|
+
const px = data.data;
|
|
99
|
+
let kr, kg, kb;
|
|
100
|
+
if (args.key === 'auto') {
|
|
101
|
+
// The median of the border pixels: robust to a stray corner of artwork.
|
|
102
|
+
const rs = [], gs = [], bs = [];
|
|
103
|
+
const sample = (x, y) => {
|
|
104
|
+
const i = (y * w + x) * 4;
|
|
105
|
+
rs.push(px[i]);
|
|
106
|
+
gs.push(px[i + 1]);
|
|
107
|
+
bs.push(px[i + 2]);
|
|
108
|
+
};
|
|
109
|
+
for (let x = 0; x < w; x += 4) {
|
|
110
|
+
sample(x, 0);
|
|
111
|
+
sample(x, h - 1);
|
|
112
|
+
}
|
|
113
|
+
for (let y = 0; y < h; y += 4) {
|
|
114
|
+
sample(0, y);
|
|
115
|
+
sample(w - 1, y);
|
|
116
|
+
}
|
|
117
|
+
const median = (values) => values.sort((a, b) => a - b)[Math.floor(values.length / 2)];
|
|
118
|
+
kr = median(rs);
|
|
119
|
+
kg = median(gs);
|
|
120
|
+
kb = median(bs);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const hex = args.key.replace('#', '');
|
|
124
|
+
kr = parseInt(hex.slice(0, 2), 16);
|
|
125
|
+
kg = parseInt(hex.slice(2, 4), 16);
|
|
126
|
+
kb = parseInt(hex.slice(4, 6), 16);
|
|
127
|
+
}
|
|
128
|
+
let minX = w, minY = h, maxX = -1, maxY = -1;
|
|
129
|
+
for (let y = 0; y < h; y++) {
|
|
130
|
+
for (let x = 0; x < w; x++) {
|
|
131
|
+
const i = (y * w + x) * 4;
|
|
132
|
+
const dr = px[i] - kr, dg = px[i + 1] - kg, db = px[i + 2] - kb;
|
|
133
|
+
const distance = Math.sqrt(dr * dr + dg * dg + db * db);
|
|
134
|
+
let alpha = 1;
|
|
135
|
+
if (distance <= args.tol)
|
|
136
|
+
alpha = 0;
|
|
137
|
+
else if (distance < args.tol + args.soft)
|
|
138
|
+
alpha = (distance - args.tol) / args.soft;
|
|
139
|
+
if (alpha < 1) {
|
|
140
|
+
if (alpha > 0) {
|
|
141
|
+
px[i] = Math.max(0, Math.min(255, (px[i] - (1 - alpha) * kr) / alpha));
|
|
142
|
+
px[i + 1] = Math.max(0, Math.min(255, (px[i + 1] - (1 - alpha) * kg) / alpha));
|
|
143
|
+
px[i + 2] = Math.max(0, Math.min(255, (px[i + 2] - (1 - alpha) * kb) / alpha));
|
|
144
|
+
}
|
|
145
|
+
px[i + 3] = Math.round(px[i + 3] * alpha);
|
|
146
|
+
}
|
|
147
|
+
if (px[i + 3] > 8) {
|
|
148
|
+
if (x < minX)
|
|
149
|
+
minX = x;
|
|
150
|
+
if (x > maxX)
|
|
151
|
+
maxX = x;
|
|
152
|
+
if (y < minY)
|
|
153
|
+
minY = y;
|
|
154
|
+
if (y > maxY)
|
|
155
|
+
maxY = y;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
ctx.putImageData(data, 0, 0);
|
|
160
|
+
if (maxX < 0)
|
|
161
|
+
throw new Error('Keying removed the whole image — the key colour matches the artwork.');
|
|
162
|
+
const left = Math.max(0, minX - args.pad);
|
|
163
|
+
const top = Math.max(0, minY - args.pad);
|
|
164
|
+
const right = Math.min(w - 1, maxX + args.pad);
|
|
165
|
+
const bottom = Math.min(h - 1, maxY + args.pad);
|
|
166
|
+
const out = document.createElement('canvas');
|
|
167
|
+
out.width = right - left + 1;
|
|
168
|
+
out.height = bottom - top + 1;
|
|
169
|
+
const outCtx = out.getContext('2d');
|
|
170
|
+
if (!outCtx)
|
|
171
|
+
throw new Error('no 2d context');
|
|
172
|
+
outCtx.drawImage(canvas, left, top, out.width, out.height, 0, 0, out.width, out.height);
|
|
173
|
+
const hex2 = (v) => Math.round(v).toString(16).padStart(2, '0');
|
|
174
|
+
return {
|
|
175
|
+
dataUrl: out.toDataURL('image/png'),
|
|
176
|
+
width: out.width,
|
|
177
|
+
height: out.height,
|
|
178
|
+
keyColor: `#${hex2(kr)}${hex2(kg)}${hex2(kb)}`,
|
|
179
|
+
};
|
|
180
|
+
/* eslint-enable no-undef */
|
|
181
|
+
}, { src: dataUrlFor(bytes), key: options.keyColor, tol: options.tolerance, soft: options.softness, pad: options.pad ?? 8 });
|
|
182
|
+
return { png: bytesFromDataUrl(result.dataUrl), width: result.width, height: result.height, keyColor: result.keyColor };
|
|
183
|
+
}
|
|
184
|
+
/** Trim fully transparent margins off a PNG, keeping `pad` pixels. */
|
|
185
|
+
async trimTransparent(png, pad = 8) {
|
|
186
|
+
const result = await this.page.evaluate(async (args) => {
|
|
187
|
+
/* eslint-disable no-undef -- browser callback */
|
|
188
|
+
const img = new Image();
|
|
189
|
+
img.src = args.src;
|
|
190
|
+
await img.decode();
|
|
191
|
+
const w = img.naturalWidth, h = img.naturalHeight;
|
|
192
|
+
const canvas = document.createElement('canvas');
|
|
193
|
+
canvas.width = w;
|
|
194
|
+
canvas.height = h;
|
|
195
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
196
|
+
if (!ctx)
|
|
197
|
+
throw new Error('no 2d context');
|
|
198
|
+
ctx.drawImage(img, 0, 0);
|
|
199
|
+
const px = ctx.getImageData(0, 0, w, h).data;
|
|
200
|
+
let minX = w, minY = h, maxX = -1, maxY = -1;
|
|
201
|
+
for (let y = 0; y < h; y++) {
|
|
202
|
+
for (let x = 0; x < w; x++) {
|
|
203
|
+
if (px[(y * w + x) * 4 + 3] > 8) {
|
|
204
|
+
if (x < minX)
|
|
205
|
+
minX = x;
|
|
206
|
+
if (x > maxX)
|
|
207
|
+
maxX = x;
|
|
208
|
+
if (y < minY)
|
|
209
|
+
minY = y;
|
|
210
|
+
if (y > maxY)
|
|
211
|
+
maxY = y;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (maxX < 0)
|
|
216
|
+
throw new Error('The image is fully transparent.');
|
|
217
|
+
const left = Math.max(0, minX - args.pad), top = Math.max(0, minY - args.pad);
|
|
218
|
+
const right = Math.min(w - 1, maxX + args.pad), bottom = Math.min(h - 1, maxY + args.pad);
|
|
219
|
+
const out = document.createElement('canvas');
|
|
220
|
+
out.width = right - left + 1;
|
|
221
|
+
out.height = bottom - top + 1;
|
|
222
|
+
const outCtx = out.getContext('2d');
|
|
223
|
+
if (!outCtx)
|
|
224
|
+
throw new Error('no 2d context');
|
|
225
|
+
outCtx.drawImage(canvas, left, top, out.width, out.height, 0, 0, out.width, out.height);
|
|
226
|
+
return { dataUrl: out.toDataURL('image/png'), width: out.width, height: out.height };
|
|
227
|
+
/* eslint-enable no-undef */
|
|
228
|
+
}, { src: dataUrlFor(png), pad });
|
|
229
|
+
return { png: bytesFromDataUrl(result.dataUrl), width: result.width, height: result.height };
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Square PNGs of `bytes` at each size, cover-cropped to the centre (or to `focus`, as fractions).
|
|
233
|
+
* Downscaling halves step by step before the last draw — one big drawImage from 1024 to 16 would
|
|
234
|
+
* alias badly even with high-quality smoothing.
|
|
235
|
+
*/
|
|
236
|
+
async squareIcons(bytes, sizes, focus = [0.5, 0.5]) {
|
|
237
|
+
const results = await this.page.evaluate(async (args) => {
|
|
238
|
+
/* eslint-disable no-undef -- browser callback */
|
|
239
|
+
const img = new Image();
|
|
240
|
+
img.src = args.src;
|
|
241
|
+
await img.decode();
|
|
242
|
+
const side = Math.min(img.naturalWidth, img.naturalHeight);
|
|
243
|
+
const sx = Math.round((img.naturalWidth - side) * args.fx);
|
|
244
|
+
const sy = Math.round((img.naturalHeight - side) * args.fy);
|
|
245
|
+
let source = document.createElement('canvas');
|
|
246
|
+
source.width = side;
|
|
247
|
+
source.height = side;
|
|
248
|
+
const sctx = source.getContext('2d');
|
|
249
|
+
if (!sctx)
|
|
250
|
+
throw new Error('no 2d context');
|
|
251
|
+
sctx.drawImage(img, sx, sy, side, side, 0, 0, side, side);
|
|
252
|
+
const out = [];
|
|
253
|
+
for (const size of [...args.sizes].sort((a, b) => b - a)) {
|
|
254
|
+
let current = source;
|
|
255
|
+
while (current.width / 2 >= size) {
|
|
256
|
+
const half = document.createElement('canvas');
|
|
257
|
+
half.width = Math.max(size, Math.floor(current.width / 2));
|
|
258
|
+
half.height = half.width;
|
|
259
|
+
const hctx = half.getContext('2d');
|
|
260
|
+
if (!hctx)
|
|
261
|
+
throw new Error('no 2d context');
|
|
262
|
+
hctx.imageSmoothingQuality = 'high';
|
|
263
|
+
hctx.drawImage(current, 0, 0, half.width, half.height);
|
|
264
|
+
current = half;
|
|
265
|
+
}
|
|
266
|
+
const final = document.createElement('canvas');
|
|
267
|
+
final.width = size;
|
|
268
|
+
final.height = size;
|
|
269
|
+
const fctx = final.getContext('2d');
|
|
270
|
+
if (!fctx)
|
|
271
|
+
throw new Error('no 2d context');
|
|
272
|
+
fctx.imageSmoothingQuality = 'high';
|
|
273
|
+
fctx.drawImage(current, 0, 0, size, size);
|
|
274
|
+
out.push({ size, dataUrl: final.toDataURL('image/png') });
|
|
275
|
+
source = current;
|
|
276
|
+
}
|
|
277
|
+
return out;
|
|
278
|
+
/* eslint-enable no-undef */
|
|
279
|
+
}, { src: dataUrlFor(bytes), sizes: [...sizes], fx: focus[0], fy: focus[1] });
|
|
280
|
+
return results
|
|
281
|
+
.map((entry) => ({ size: entry.size, png: bytesFromDataUrl(entry.dataUrl) }))
|
|
282
|
+
.sort((a, b) => a.size - b.size);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=raster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raster.js","sourceRoot":"","sources":["../../src/steam/raster.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const SHELL_SCHEME = "bitmagic";
|
|
2
|
+
export declare const SHELL_HOST = "game";
|
|
3
|
+
export interface ShellIdentity {
|
|
4
|
+
/** Window title, About panel. */
|
|
5
|
+
displayName: string;
|
|
6
|
+
/** Stable per game: names the saves directory, so it must never follow a rename. */
|
|
7
|
+
packageName: string;
|
|
8
|
+
version: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function shellPackageName(gameId: string): string;
|
|
11
|
+
export declare function buildShellPackageJson(identity: ShellIdentity): string;
|
|
12
|
+
export declare function buildShellMainJs(identity: ShellIdentity): string;
|
|
13
|
+
/**
|
|
14
|
+
* The page's view of the shell: feature-detectable as `window.bitmagicDesktop`, so a game can
|
|
15
|
+
* offer "Quit" and a fullscreen toggle only where they mean something.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SHELL_PRELOAD_JS = "// Generated by `bitmagic steam build`.\n'use strict';\n\nconst { contextBridge, ipcRenderer } = require('electron');\n\ncontextBridge.exposeInMainWorld('bitmagicDesktop', {\n platform: process.platform,\n store: 'steam',\n quit: () => ipcRenderer.send('bitmagic:quit'),\n setFullscreen: (on) => ipcRenderer.send('bitmagic:fullscreen', on === true),\n isFullscreen: () => ipcRenderer.sendSync('bitmagic:is-fullscreen'),\n});\n";
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The desktop shell's own files — `main.js`, `preload.js`, `package.json` — written into the
|
|
3
|
+
* Electron app next to the game payload.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately dependency-free: the pinned Electron is the shell's whole attack surface. No
|
|
6
|
+
* self-updater, ever: Steam updates the game through its depots, and a second updater would fight
|
|
7
|
+
* it.
|
|
8
|
+
*
|
|
9
|
+
* The game is served from a privileged `bitmagic://game/` scheme rather than `file://`: the
|
|
10
|
+
* payload is a page plus a vendor tree of assets the engine `fetch`es, which Chromium refuses over
|
|
11
|
+
* file://, and a standard secure scheme also gives the page a stable origin — localStorage and
|
|
12
|
+
* IndexedDB (the game's saves) live under it, and WebGPU needs the secure context. The MIME table
|
|
13
|
+
* is generated from the SAME map the offline validation server uses (package/local-server.ts), so
|
|
14
|
+
* the shipped app answers exactly as the browser that validated the payload did — the iOS lane's
|
|
15
|
+
* rule for its Swift handler.
|
|
16
|
+
*/
|
|
17
|
+
import { MIME_TYPES, FALLBACK_MIME } from '../package/local-server.js';
|
|
18
|
+
export const SHELL_SCHEME = 'bitmagic';
|
|
19
|
+
export const SHELL_HOST = 'game';
|
|
20
|
+
export function shellPackageName(gameId) {
|
|
21
|
+
return `bitmagic-${gameId.toLowerCase().replace(/[^a-z0-9-]/g, '') || 'game'}`;
|
|
22
|
+
}
|
|
23
|
+
export function buildShellPackageJson(identity) {
|
|
24
|
+
return `${JSON.stringify({
|
|
25
|
+
name: identity.packageName,
|
|
26
|
+
productName: identity.displayName,
|
|
27
|
+
version: identity.version,
|
|
28
|
+
private: true,
|
|
29
|
+
main: 'main.js',
|
|
30
|
+
}, null, 2)}\n`;
|
|
31
|
+
}
|
|
32
|
+
export function buildShellMainJs(identity) {
|
|
33
|
+
const mime = JSON.stringify(Object.fromEntries(MIME_TYPES), null, 2);
|
|
34
|
+
return `// Generated by \`bitmagic steam build\`. The desktop shell for a Bitmagic game.
|
|
35
|
+
'use strict';
|
|
36
|
+
|
|
37
|
+
const { app, BrowserWindow, Menu, ipcMain, protocol, session, shell } = require('electron');
|
|
38
|
+
const fs = require('fs');
|
|
39
|
+
const path = require('path');
|
|
40
|
+
|
|
41
|
+
const DISPLAY_NAME = ${JSON.stringify(identity.displayName)};
|
|
42
|
+
const PACKAGE_NAME = ${JSON.stringify(identity.packageName)};
|
|
43
|
+
const SCHEME = ${JSON.stringify(SHELL_SCHEME)};
|
|
44
|
+
const HOST = ${JSON.stringify(SHELL_HOST)};
|
|
45
|
+
const GAME_DIR = path.join(__dirname, 'game');
|
|
46
|
+
const MIME = ${mime};
|
|
47
|
+
const FALLBACK_MIME = ${JSON.stringify(FALLBACK_MIME)};
|
|
48
|
+
|
|
49
|
+
// Saves live in localStorage/IndexedDB under userData. Pinned to the game's id, not its display
|
|
50
|
+
// name, so renaming the game on Steam never strands a player's progress.
|
|
51
|
+
// BITMAGIC_SHELL_CHECK is set only by \`bitmagic steam build\` when it launches the finished app to
|
|
52
|
+
// prove it reaches gameplay: that run gets a throwaway profile and a window instead of fullscreen.
|
|
53
|
+
const CHECK_DIR = process.env.BITMAGIC_SHELL_CHECK || '';
|
|
54
|
+
app.setPath('userData', CHECK_DIR || path.join(app.getPath('appData'), PACKAGE_NAME));
|
|
55
|
+
app.setName(DISPLAY_NAME);
|
|
56
|
+
|
|
57
|
+
protocol.registerSchemesAsPrivileged([{
|
|
58
|
+
scheme: SCHEME,
|
|
59
|
+
privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true, stream: true, codeCache: true },
|
|
60
|
+
}]);
|
|
61
|
+
|
|
62
|
+
// A game renders every frame, focused or not.
|
|
63
|
+
app.commandLine.appendSwitch('disable-renderer-backgrounding');
|
|
64
|
+
app.commandLine.appendSwitch('disable-background-timer-throttling');
|
|
65
|
+
// Chromium's GPU blocklist would put an older driver on the software rasterizer, which no 3D game
|
|
66
|
+
// survives; a game wants the GPU it was given.
|
|
67
|
+
app.commandLine.appendSwitch('ignore-gpu-blocklist');
|
|
68
|
+
// Under Proton (Steam Deck, Linux) the Windows build runs in Wine, where Chromium's sandbox cannot
|
|
69
|
+
// start. Steam sets these variables for every game it launches through Proton.
|
|
70
|
+
if (process.platform === 'win32' && (process.env.STEAM_COMPAT_DATA_PATH || process.env.SteamDeck === '1')) {
|
|
71
|
+
app.commandLine.appendSwitch('no-sandbox');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!app.requestSingleInstanceLock()) {
|
|
75
|
+
app.quit();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveGameFile(url) {
|
|
79
|
+
let pathname;
|
|
80
|
+
try {
|
|
81
|
+
const parsed = new URL(url);
|
|
82
|
+
if (parsed.host !== HOST) return null;
|
|
83
|
+
pathname = decodeURIComponent(parsed.pathname);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const file = path.resolve(GAME_DIR, '.' + pathname);
|
|
88
|
+
if (file !== GAME_DIR && !file.startsWith(GAME_DIR + path.sep)) return null;
|
|
89
|
+
try {
|
|
90
|
+
return fs.statSync(file).isDirectory() ? path.join(file, 'index.html') : file;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function mimeFor(file) {
|
|
97
|
+
const ext = path.extname(file).slice(1).toLowerCase();
|
|
98
|
+
return MIME[ext] || FALLBACK_MIME;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function serve(request) {
|
|
102
|
+
const file = resolveGameFile(request.url);
|
|
103
|
+
if (file === null) return new Response('not found', { status: 404 });
|
|
104
|
+
let bytes;
|
|
105
|
+
try {
|
|
106
|
+
bytes = await fs.promises.readFile(file);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
return new Response('not found', { status: 404 });
|
|
109
|
+
}
|
|
110
|
+
const headers = { 'content-type': mimeFor(file), 'cache-control': 'no-store', 'accept-ranges': 'bytes' };
|
|
111
|
+
const range = /^bytes=(\\d*)-(\\d*)$/.exec(request.headers.get('range') || '');
|
|
112
|
+
if (range && (range[1] !== '' || range[2] !== '')) {
|
|
113
|
+
const size = bytes.length;
|
|
114
|
+
const start = range[1] === '' ? Math.max(0, size - Number(range[2])) : Number(range[1]);
|
|
115
|
+
const end = range[1] === '' || range[2] === '' ? size - 1 : Math.min(size - 1, Number(range[2]));
|
|
116
|
+
if (start > end || start >= size) return new Response('', { status: 416, headers: { 'content-range': 'bytes */' + size } });
|
|
117
|
+
return new Response(bytes.subarray(start, end + 1), {
|
|
118
|
+
status: 206,
|
|
119
|
+
headers: Object.assign(headers, { 'content-range': 'bytes ' + start + '-' + end + '/' + size }),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return new Response(bytes, { status: 200, headers });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function stateFile() {
|
|
126
|
+
return path.join(app.getPath('userData'), 'window-state.json');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function loadState() {
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(fs.readFileSync(stateFile(), 'utf8'));
|
|
132
|
+
} catch (err) {
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function saveState(win) {
|
|
138
|
+
try {
|
|
139
|
+
const bounds = win.getNormalBounds();
|
|
140
|
+
fs.writeFileSync(stateFile(), JSON.stringify({
|
|
141
|
+
x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height,
|
|
142
|
+
fullscreen: win.isFullScreen(),
|
|
143
|
+
}));
|
|
144
|
+
} catch (err) {
|
|
145
|
+
// Window placement is a convenience.
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function buildMenu() {
|
|
150
|
+
if (process.platform !== 'darwin') return null;
|
|
151
|
+
return Menu.buildFromTemplate([
|
|
152
|
+
{
|
|
153
|
+
label: DISPLAY_NAME,
|
|
154
|
+
submenu: [
|
|
155
|
+
{ role: 'about' },
|
|
156
|
+
{ type: 'separator' },
|
|
157
|
+
{ role: 'hide' },
|
|
158
|
+
{ role: 'hideOthers' },
|
|
159
|
+
{ role: 'unhide' },
|
|
160
|
+
{ type: 'separator' },
|
|
161
|
+
{ role: 'quit' },
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
{ label: 'View', submenu: [{ role: 'togglefullscreen' }] },
|
|
165
|
+
{ role: 'windowMenu' },
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function createWindow() {
|
|
170
|
+
const state = loadState();
|
|
171
|
+
const win = new BrowserWindow({
|
|
172
|
+
title: DISPLAY_NAME,
|
|
173
|
+
width: state.width || 1280,
|
|
174
|
+
height: state.height || 720,
|
|
175
|
+
x: typeof state.x === 'number' ? state.x : undefined,
|
|
176
|
+
y: typeof state.y === 'number' ? state.y : undefined,
|
|
177
|
+
minWidth: 640,
|
|
178
|
+
minHeight: 360,
|
|
179
|
+
show: false,
|
|
180
|
+
backgroundColor: '#000000',
|
|
181
|
+
autoHideMenuBar: true,
|
|
182
|
+
// Games open fullscreen; the player's own choice is remembered from then on.
|
|
183
|
+
fullscreen: CHECK_DIR ? false : state.fullscreen !== false,
|
|
184
|
+
webPreferences: {
|
|
185
|
+
preload: path.join(__dirname, 'preload.js'),
|
|
186
|
+
contextIsolation: true,
|
|
187
|
+
nodeIntegration: false,
|
|
188
|
+
sandbox: true,
|
|
189
|
+
backgroundThrottling: false,
|
|
190
|
+
autoplayPolicy: 'no-user-gesture-required',
|
|
191
|
+
spellcheck: false,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
win.setMenuBarVisibility(false);
|
|
195
|
+
win.once('ready-to-show', () => win.show());
|
|
196
|
+
|
|
197
|
+
win.webContents.on('before-input-event', (event, input) => {
|
|
198
|
+
if (input.type !== 'keyDown') return;
|
|
199
|
+
if (input.key === 'F11' || (input.key === 'Enter' && input.alt)) {
|
|
200
|
+
win.setFullScreen(!win.isFullScreen());
|
|
201
|
+
event.preventDefault();
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
// Links leave the game for the system browser; nothing navigates the game's own window away.
|
|
205
|
+
win.webContents.setWindowOpenHandler(({ url }) => {
|
|
206
|
+
if (/^https?:\\/\\//.test(url)) shell.openExternal(url);
|
|
207
|
+
return { action: 'deny' };
|
|
208
|
+
});
|
|
209
|
+
win.webContents.on('will-navigate', (event, url) => {
|
|
210
|
+
if (!url.startsWith(SCHEME + '://')) {
|
|
211
|
+
event.preventDefault();
|
|
212
|
+
if (/^https?:\\/\\//.test(url)) shell.openExternal(url);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
win.on('close', () => saveState(win));
|
|
216
|
+
win.loadURL(SCHEME + '://' + HOST + '/index.html');
|
|
217
|
+
return win;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
app.on('second-instance', () => {
|
|
221
|
+
const win = BrowserWindow.getAllWindows()[0];
|
|
222
|
+
if (win) {
|
|
223
|
+
if (win.isMinimized()) win.restore();
|
|
224
|
+
win.focus();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
app.whenReady().then(() => {
|
|
229
|
+
protocol.handle(SCHEME, serve);
|
|
230
|
+
// The game asks for nothing beyond fullscreen, pointer lock and gamepads.
|
|
231
|
+
session.defaultSession.setPermissionRequestHandler((_wc, permission, callback) => {
|
|
232
|
+
callback(['fullscreen', 'pointerLock', 'clipboard-sanitized-write'].includes(permission));
|
|
233
|
+
});
|
|
234
|
+
Menu.setApplicationMenu(buildMenu());
|
|
235
|
+
app.setAboutPanelOptions({ applicationName: DISPLAY_NAME, applicationVersion: ${JSON.stringify(identity.version)} });
|
|
236
|
+
ipcMain.on('bitmagic:quit', () => app.quit());
|
|
237
|
+
ipcMain.on('bitmagic:fullscreen', (event, on) => {
|
|
238
|
+
const win = BrowserWindow.fromWebContents(event.sender);
|
|
239
|
+
if (win) win.setFullScreen(on === true);
|
|
240
|
+
});
|
|
241
|
+
ipcMain.on('bitmagic:is-fullscreen', (event) => {
|
|
242
|
+
const win = BrowserWindow.fromWebContents(event.sender);
|
|
243
|
+
event.returnValue = win ? win.isFullScreen() : false;
|
|
244
|
+
});
|
|
245
|
+
createWindow();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
app.on('window-all-closed', () => app.quit());
|
|
249
|
+
`;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The page's view of the shell: feature-detectable as `window.bitmagicDesktop`, so a game can
|
|
253
|
+
* offer "Quit" and a fullscreen toggle only where they mean something.
|
|
254
|
+
*/
|
|
255
|
+
export const SHELL_PRELOAD_JS = `// Generated by \`bitmagic steam build\`.
|
|
256
|
+
'use strict';
|
|
257
|
+
|
|
258
|
+
const { contextBridge, ipcRenderer } = require('electron');
|
|
259
|
+
|
|
260
|
+
contextBridge.exposeInMainWorld('bitmagicDesktop', {
|
|
261
|
+
platform: process.platform,
|
|
262
|
+
store: 'steam',
|
|
263
|
+
quit: () => ipcRenderer.send('bitmagic:quit'),
|
|
264
|
+
setFullscreen: (on) => ipcRenderer.send('bitmagic:fullscreen', on === true),
|
|
265
|
+
isFullscreen: () => ipcRenderer.sendSync('bitmagic:is-fullscreen'),
|
|
266
|
+
});
|
|
267
|
+
`;
|
|
268
|
+
//# sourceMappingURL=shell-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-files.js","sourceRoot":"","sources":["../../src/steam/shell-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEvE,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC;AACvC,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC;AAUjC,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,OAAO,YAAY,MAAM,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,QAAuB;IAC3D,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;QACvB,IAAI,EAAE,QAAQ,CAAC,WAAW;QAC1B,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,SAAS;KAChB,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,QAAuB;IACtD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACrE,OAAO;;;;;;;uBAOc,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC;uBACpC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC;iBAC1C,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;eAC9B,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;;eAE1B,IAAI;wBACK,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kFA4L6B,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC;;;;;;;;;;;;;;CAcjH,CAAC;AACF,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;CAY/B,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every image Steamworks asks for, at the size it asks for it, with the rule Valve reviews it
|
|
3
|
+
* against. One table, read by the renderer (what to draw), the check (what must exist) and the
|
|
4
|
+
* skill (what goes where) — a size that lived in three places would drift in one of them.
|
|
5
|
+
*
|
|
6
|
+
* Sizes and rules from partner.steamgames.com/doc/store/assets/standard and
|
|
7
|
+
* /doc/store/assets/libraryassets, as of 2026-10.
|
|
8
|
+
*/
|
|
9
|
+
/** Which source picture an asset is cut from. */
|
|
10
|
+
export type ArtSource = 'wide' | 'tall' | 'hero' | 'icon';
|
|
11
|
+
export type LogoRule =
|
|
12
|
+
/** The game's logo must be on it and legible — every capsule. */
|
|
13
|
+
'required'
|
|
14
|
+
/** No text or logo at all — the library hero, the page background. */
|
|
15
|
+
| 'forbidden'
|
|
16
|
+
/** The asset IS the logo, on a transparent background. */
|
|
17
|
+
| 'only';
|
|
18
|
+
export interface SteamImageSpec {
|
|
19
|
+
id: string;
|
|
20
|
+
/** Relative to the store kit directory. */
|
|
21
|
+
file: string;
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
format: 'png' | 'jpg';
|
|
25
|
+
/** The picture it is cut from; null for the logo, which is the logo itself. */
|
|
26
|
+
art: ArtSource | null;
|
|
27
|
+
logo: LogoRule;
|
|
28
|
+
/** Required before the store page can be reviewed. */
|
|
29
|
+
required: boolean;
|
|
30
|
+
/** Where it is uploaded in Steamworks. */
|
|
31
|
+
where: string;
|
|
32
|
+
/** Valve's rule for it, in a line. */
|
|
33
|
+
rule: string;
|
|
34
|
+
}
|
|
35
|
+
export declare const STORE_IMAGE_SPECS: readonly SteamImageSpec[];
|
|
36
|
+
/** The multi-size Windows icon and the macOS icon, written next to the shortcut icon. */
|
|
37
|
+
export declare const SHORTCUT_ICO_FILE = "icons/shortcut_icon.ico";
|
|
38
|
+
export declare const MAC_ICNS_FILE = "icons/mac_icon.icns";
|
|
39
|
+
/** The library logo is "1280 wide and/or 720 tall": it keeps its own aspect inside that box. */
|
|
40
|
+
export declare const LIBRARY_LOGO_BOX: {
|
|
41
|
+
readonly width: 1280;
|
|
42
|
+
readonly height: 720;
|
|
43
|
+
};
|
|
44
|
+
export declare const SCREENSHOT_WIDTH = 1920;
|
|
45
|
+
export declare const SCREENSHOT_HEIGHT = 1080;
|
|
46
|
+
/** Fewer than five and the store page cannot be submitted. */
|
|
47
|
+
export declare const MIN_SCREENSHOTS = 5;
|
|
48
|
+
/** Enough for a varied gallery without padding it with near-duplicates. */
|
|
49
|
+
export declare const RECOMMENDED_SCREENSHOTS = 8;
|
|
50
|
+
/** The icon sizes the Windows .ico and the macOS .icns carry. */
|
|
51
|
+
export declare const ICO_SIZES: readonly [16, 24, 32, 48, 64, 128, 256];
|
|
52
|
+
export declare const ICNS_SIZES: readonly [16, 32, 64, 128, 256, 512, 1024];
|
|
53
|
+
/** Steam's trailer rules (partner.steamgames.com/doc/store/trailer). */
|
|
54
|
+
export declare const TRAILER_RULES: {
|
|
55
|
+
readonly maxWidth: 1920;
|
|
56
|
+
readonly maxHeight: 1080;
|
|
57
|
+
readonly frameRates: readonly [30, 29.97, 60, 59.94];
|
|
58
|
+
readonly minBitrateKbps: 5000;
|
|
59
|
+
readonly containers: readonly ["mp4", "mov", "wmv"];
|
|
60
|
+
};
|
|
61
|
+
export declare function specById(id: string): SteamImageSpec | undefined;
|