@next2d/builder 0.1.10 → 0.2.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 (49) hide show
  1. package/README.md +88 -57
  2. package/dist/cli.d.ts +7 -0
  3. package/dist/cli.js +68 -3
  4. package/dist/colors.d.ts +6 -0
  5. package/dist/colors.js +7 -0
  6. package/dist/context.d.ts +10 -1
  7. package/dist/context.js +7 -0
  8. package/dist/description.d.ts +2 -0
  9. package/dist/description.js +8 -0
  10. package/dist/electron-config.d.ts +52 -0
  11. package/dist/electron-config.js +116 -0
  12. package/dist/electron-host.d.ts +5 -0
  13. package/dist/electron-host.js +42 -0
  14. package/dist/electron.d.ts +4 -9
  15. package/dist/electron.js +83 -132
  16. package/dist/index.js +21 -0
  17. package/dist/native.d.ts +9 -24
  18. package/dist/native.js +48 -106
  19. package/dist/project-vite.d.ts +3 -0
  20. package/dist/project-vite.js +21 -0
  21. package/dist/resolve-tool-packages.d.ts +1 -0
  22. package/dist/resolve-tool-packages.js +29 -0
  23. package/dist/steam-upload.d.ts +27 -0
  24. package/dist/steam-upload.js +131 -0
  25. package/dist/steam.d.ts +22 -0
  26. package/dist/steam.js +193 -0
  27. package/dist/tool-packages.d.ts +15 -0
  28. package/dist/tool-packages.js +74 -0
  29. package/dist/web.js +10 -4
  30. package/dist/xbox.d.ts +1 -1
  31. package/dist/xbox.js +60 -57
  32. package/docs/capacitor.md +201 -0
  33. package/docs/steam.md +714 -0
  34. package/docs/xbox.md +145 -0
  35. package/package.json +11 -14
  36. package/templates/electron/entitlements.plist +12 -0
  37. package/templates/electron/icons/icon.icns +0 -0
  38. package/templates/electron/icons/icon.ico +0 -0
  39. package/templates/electron/icons/icon.png +0 -0
  40. package/templates/electron/index.js +95 -0
  41. package/templates/electron/local-assets.cjs +24 -0
  42. package/templates/electron/package.json +11 -0
  43. package/templates/xbox/src/platform/StbTextRasterizer.cpp +14 -10
  44. package/templates/xbox/tests/check_local.sh +12 -4
  45. package/templates/xbox/tests/stb_safety_test.cpp +126 -0
  46. package/templates/xbox/third_party/LICENSES.md +15 -0
  47. package/templates/xbox/third_party/stb_image.h +15 -10
  48. package/templates/xbox/third_party/stb_truetype.h +4 -4
  49. package/templates/xbox/third_party/stb_vorbis.c +1 -1
package/docs/xbox.md ADDED
@@ -0,0 +1,145 @@
1
+ # Xbox(試作・開発段階 / Prototype under development)
2
+
3
+ [日本語](#日本語) | [English](#english)
4
+
5
+ ## 日本語
6
+
7
+ Xbox対応はGDK向けネイティブホストの試作・開発段階。Xbox実機での動作確認・配布対応は完了していない。
8
+ 以下は開発環境での検証手順。
9
+
10
+ ### STEP1:検証範囲に必要な環境を用意する
11
+
12
+ | 検証範囲 | ユーザーが用意するもの |
13
+ |---|---|
14
+ | ホスト生成・アセット準備のみ | macOS / Linuxでも実行できる。 |
15
+ | C++ホストのビルド | Windows、Visual Studio 2022、Microsoft GDK、CMake 3.26以降。現行builderはVisual Studio 2022ジェネレーターを指定する。 |
16
+ | Xbox実機での検証 | コンソール向けGDK開発環境とXbox開発機(devkit)。PC向けのGDK検証環境とは区別して用意する。 |
17
+
18
+ ### STEP2:ゲーム設定とV8を用意する
19
+
20
+ ゲームルートの `MicrosoftGame.config` にゲーム固有の値を設定する。
21
+ 未配置の場合は初回のホスト生成でテンプレートから作られるため、生成後に内容を確認・変更する。
22
+
23
+ V8はJavaScript実行エンジン。通常はbuilderがビルド済みライブラリを取得し、自前ビルドを使う場合だけパスを指定する。
24
+ 描画用のDawn(WebGPU → D3D12)はCMakeのFetchContentが取得するため、初回取得にはネットワーク接続が必要。
25
+
26
+ Windowsでのビルド時は、次の優先順でV8を解決する。
27
+
28
+ 1. `--v8-root` で指定したパス
29
+ 2. 環境変数 `V8_ROOT`
30
+ 3. キャッシュ済みのビルド済みV8
31
+ 4. Next2D/builderのGitHub Releasesからダウンロード
32
+
33
+ ビルド済みV8は `build-v8` ワークフローで作成・公開する構成になっている。
34
+ 取得対象のバージョンとリビジョンはbuilder側で固定し、取得後はキャッシュを再利用する。
35
+ 自前のV8を使用する場合:
36
+
37
+ ```bat
38
+ npx @next2d/builder --platform xbox --env prd --v8-root C:\path\to\v8
39
+ ```
40
+
41
+ 公開しているWindows x64用V8の利用と、Xboxコンソール向けの互換性検証は別の作業。
42
+ コンソール向けにはGDKツールチェーンでの調整・再ビルドを含む検証が残っている。
43
+
44
+ ### STEP3:ホストを生成・ビルドする
45
+
46
+ ゲームルートで実行する。
47
+
48
+ ```sh
49
+ npx @next2d/builder --platform xbox --env prd
50
+ ```
51
+
52
+ builder同梱のC++ / CMakeテンプレートから `xbox/` を生成・更新し、`MicrosoftGame.config` を反映する。
53
+ Webビルド結果とホストスクリプトを `assets.pak` / `assets.rc` にまとめ、実行ファイルへの埋め込み用に配置する。
54
+ このホストはV8とDawnを使い、ElectronやWebViewは使用しない。
55
+
56
+ | 実行環境・引数 | コマンドの到達点 |
57
+ |---|---|
58
+ | Windows | V8を解決し、CMakeでGDK向けの構成・ビルドを実行する。対象環境での検証が必要。 |
59
+ | Windows + `--open` / `--preview` | CMakeで構成した後、Visual Studioのソリューションを開く。実行・デバッグはVisual Studio側で行う。 |
60
+ | macOS / Linux | ホスト生成とアセット準備までで終了する。GDK向け実行ファイルは生成しない。 |
61
+
62
+ ### STEP4:生成物と対象環境での動作を確認する
63
+
64
+ コマンドの終了だけでXbox向けの配布物が完成したとは判断せず、生成物とログを確認する。
65
+
66
+ `xbox-host-ci` ワークフローには、ラスタライザーとstbの回帰テスト、Windows APIを使う一部機能のテスト、
67
+ V8依存ソースのコンパイル確認、Game Core APIの確認を用意している。
68
+ これらはホスト全体のリンクやXbox実機での動作を保証するものではない。
69
+ Dawn / GDKを含む統合ビルドと、ゲームを使った実機検証が必要。
70
+
71
+ 今後の検証・調整対象は、描画、Worker、入力、音声、テキスト、保存処理、性能、配布設定など。
72
+ 詳細なホスト構成・自前V8のビルド・機能ごとの実装状況は、
73
+ [ホストテンプレートの開発資料](../templates/xbox/README.md)を参照する。
74
+ 同じ資料は生成先の `xbox/README.md` にも配置される。
75
+
76
+ ## English
77
+
78
+ Xbox support is a prototype GDK native host under development. Xbox hardware validation and distribution support are not complete.
79
+ The following steps are for development testing.
80
+
81
+ ### STEP1: Prepare the environment for your validation scope
82
+
83
+ | Validation scope | What you need to prepare |
84
+ |---|---|
85
+ | Host generation and asset preparation only | Also works on macOS / Linux. |
86
+ | Building the C++ host | Windows, Visual Studio 2022, Microsoft GDK and CMake 3.26 or later. The current builder selects the Visual Studio 2022 generator. |
87
+ | Testing on Xbox hardware | A console GDK development environment and Xbox development hardware (devkit), distinct from a PC GDK testing environment. |
88
+
89
+ ### STEP2: Prepare game configuration and V8
90
+
91
+ Set game-specific values in `MicrosoftGame.config` at the game root.
92
+ If absent, it is created from the template during initial host generation; review and edit it afterward.
93
+
94
+ V8 is the JavaScript engine. The builder normally downloads a prebuilt library; specify a path only when using your own build.
95
+ CMake FetchContent acquires Dawn (WebGPU → D3D12) for rendering, so the first download requires network access.
96
+
97
+ For Windows builds, V8 is resolved in this order:
98
+
99
+ 1. The path passed with `--v8-root`
100
+ 2. The `V8_ROOT` environment variable
101
+ 3. A cached prebuilt V8
102
+ 4. A download from the Next2D/builder GitHub Releases
103
+
104
+ The `build-v8` workflow is configured to build and publish prebuilt V8 libraries.
105
+ The builder pins the download version and revision and reuses the cache after downloading.
106
+ To use your own V8 build:
107
+
108
+ ```bat
109
+ npx @next2d/builder --platform xbox --env prd --v8-root C:\path\to\v8
110
+ ```
111
+
112
+ Using the published Windows x64 V8 library and validating Xbox console compatibility are separate tasks.
113
+ Console support still needs validation, including adjustments or rebuilds with the GDK toolchain.
114
+
115
+ ### STEP3: Generate and build the host
116
+
117
+ Run from the game root:
118
+
119
+ ```sh
120
+ npx @next2d/builder --platform xbox --env prd
121
+ ```
122
+
123
+ The builder creates or updates `xbox/` from its bundled C++ / CMake templates and applies `MicrosoftGame.config`.
124
+ It packs the web build output and host scripts into `assets.pak` / `assets.rc` for embedding in the executable.
125
+ The host uses V8 and Dawn, without Electron or WebView.
126
+
127
+ | Environment / options | What the command completes |
128
+ |---|---|
129
+ | Windows | Resolves V8, then configures and builds the GDK target with CMake. Target-environment validation is still needed. |
130
+ | Windows + `--open` / `--preview` | Opens the Visual Studio solution after CMake configuration. Run and debug from Visual Studio. |
131
+ | macOS / Linux | Stops after host generation and asset preparation; no GDK executable is built. |
132
+
133
+ ### STEP4: Check the output and target-environment behavior
134
+
135
+ Command completion alone does not mean an Xbox distribution package is ready; inspect the output and logs.
136
+
137
+ The `xbox-host-ci` workflow includes rasterizer and stb regression tests, tests for selected Windows API functionality,
138
+ compilation checks for V8-dependent sources and Game Core API checks.
139
+ These checks do not guarantee that the complete host links or runs on Xbox hardware.
140
+ An integrated build with Dawn / GDK and testing with a game on the target hardware are still required.
141
+
142
+ Remaining validation and adjustments cover rendering, Workers, input, audio, text, save handling, performance and distribution settings.
143
+ For the detailed host architecture, instructions for building V8 yourself and implementation status by feature,
144
+ see the [host template development notes](../templates/xbox/README.md).
145
+ The same notes are copied to the generated `xbox/README.md`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next2d/builder",
3
- "version": "0.1.10",
3
+ "version": "0.2.0",
4
4
  "description": "Multi-platform builder for Next2d Framework, supporting export to various platforms such as macOS, Windows, iOS, Android and Web(HTML)",
5
5
  "author": "Toshiyuki Ienaga <ienaga@next2d.app>",
6
6
  "license": "MIT",
@@ -29,7 +29,8 @@
29
29
  },
30
30
  "files": [
31
31
  "dist",
32
- "templates"
32
+ "templates",
33
+ "docs"
33
34
  ],
34
35
  "repository": {
35
36
  "type": "git",
@@ -38,20 +39,16 @@
38
39
  "bin": {
39
40
  "@next2d/builder": "dist/index.js"
40
41
  },
41
- "dependencies": {
42
- "@electron-forge/core": "^7.11.2",
43
- "@types/node": "^26.1.2",
44
- "picocolors": "^1.1.1",
45
- "vite": "^8.2.0"
46
- },
47
42
  "devDependencies": {
48
- "@eslint/eslintrc": "^3.3.6",
43
+ "@eslint/eslintrc": "^3.3.7",
49
44
  "@eslint/js": "^10.0.1",
50
- "@typescript-eslint/eslint-plugin": "^8.65.0",
51
- "@typescript-eslint/parser": "^8.65.0",
52
- "eslint": "^10.8.0",
45
+ "@types/node": "^26.6.2",
46
+ "@typescript-eslint/eslint-plugin": "^8.70.1",
47
+ "@typescript-eslint/parser": "^8.70.1",
48
+ "eslint": "^10.11.0",
53
49
  "eslint-plugin-unused-imports": "^4.4.1",
54
- "globals": "^17.8.0",
55
- "typescript": "^6.0.3"
50
+ "globals": "^17.12.0",
51
+ "typescript": "^6.0.3",
52
+ "vite": "^8.3.0"
56
53
  }
57
54
  }
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>com.apple.security.cs.allow-jit</key>
6
+ <true/>
7
+ <key>com.apple.security.cs.disable-library-validation</key>
8
+ <true/>
9
+ <key>com.apple.security.cs.allow-dyld-environment-variables</key>
10
+ <true/>
11
+ </dict>
12
+ </plist>
@@ -0,0 +1,95 @@
1
+ const { app, BrowserWindow, Menu, net, protocol, screen, session } = require("electron");
2
+ const path = require("node:path");
3
+ const fs = require("node:fs");
4
+ const { pathToFileURL } = require("node:url");
5
+ const { resolveAssetPath, CSP } = require("./local-assets.cjs");
6
+ const config = require("./runtime-config.json");
7
+ // Keep saves in a stable user directory even if the visible game name changes.
8
+ app.setName(config.appName);
9
+ app.setPath("userData", path.join(app.getPath("appData"), config.appId));
10
+ const assetRoot = app.isPackaged
11
+ ? path.join(process.resourcesPath, "resources")
12
+ : path.join(__dirname, "resources");
13
+
14
+ // A fixed secure origin supports fetch, module scripts, workers and localStorage.
15
+ protocol.registerSchemesAsPrivileged([{
16
+ scheme: "next2d",
17
+ privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true, stream: true }
18
+ }]);
19
+
20
+ const startURL = "next2d://game/index.html";
21
+ let mainWindow;
22
+ const createWindow = async () => {
23
+ const { width, height } = screen.getPrimaryDisplay().workAreaSize;
24
+ mainWindow = new BrowserWindow({
25
+ width: Math.min(config.window.width, width),
26
+ height: Math.min(config.window.height, height),
27
+ title: config.appName,
28
+ fullscreen: config.window.fullscreen,
29
+ ...(config.icon ? { icon: path.join(assetRoot, config.icon) } : {}),
30
+ useContentSize: true,
31
+ backgroundColor: "#000000",
32
+ autoHideMenuBar: true,
33
+ show: false,
34
+ webPreferences: { nodeIntegration: false, contextIsolation: true, sandbox: true }
35
+ });
36
+ mainWindow.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
37
+ mainWindow.webContents.on("will-navigate", (event, url) => {
38
+ if (!resolveAssetPath(assetRoot, url)) {
39
+ event.preventDefault();
40
+ }
41
+ });
42
+ mainWindow.webContents.on("before-input-event", (event, input) => {
43
+ if (input.type !== "keyDown") {
44
+ return;
45
+ }
46
+ if (input.key === "F11" || (input.alt && input.key === "Enter")) {
47
+ mainWindow.setFullScreen(!mainWindow.isFullScreen());
48
+ event.preventDefault();
49
+ } else if (input.key === "Escape" && mainWindow.isFullScreen()) {
50
+ mainWindow.setFullScreen(false);
51
+ event.preventDefault();
52
+ }
53
+ });
54
+ mainWindow.webContents.on("render-process-gone", (_event, details) => {
55
+ console.error("Renderer exited:", details.reason);
56
+ app.exit(1);
57
+ });
58
+ mainWindow.once("ready-to-show", () => mainWindow.show());
59
+ await mainWindow.loadURL(startURL);
60
+ };
61
+
62
+ app.whenReady().then(async () => {
63
+ Menu.setApplicationMenu(process.platform === "darwin" ? Menu.buildFromTemplate([
64
+ { role: "appMenu" }, { role: "editMenu" }, { role: "windowMenu" }
65
+ ]) : null);
66
+ session.defaultSession.setPermissionRequestHandler((_contents, permission, callback) => {
67
+ callback(permission === "fullscreen");
68
+ });
69
+ session.defaultSession.setPermissionCheckHandler((_contents, permission) => permission === "fullscreen");
70
+ protocol.handle("next2d", async (request) => {
71
+ let asset = resolveAssetPath(assetRoot, request.url);
72
+ if (!asset || request.method !== "GET") {
73
+ return new Response("Not found", { status: 404 });
74
+ }
75
+ // Framework SPA routes use history.pushState (e.g. /home); reload the entry.
76
+ if (!path.extname(asset) && !fs.existsSync(asset)) {
77
+ asset = path.join(assetRoot, "index.html");
78
+ }
79
+ try {
80
+ const response = await net.fetch(pathToFileURL(asset).href);
81
+ const headers = new Headers(response.headers);
82
+ headers.set("Content-Security-Policy", CSP);
83
+ return new Response(response.body, { status: response.status, headers });
84
+ } catch {
85
+ return new Response("Not found", { status: 404 });
86
+ }
87
+ });
88
+ await createWindow();
89
+ }).catch((error) => {
90
+ console.error(error);
91
+ app.exit(1);
92
+ });
93
+
94
+ // Closing the game must stop Steam's "Running" status, including on macOS.
95
+ app.on("window-all-closed", () => app.quit());
@@ -0,0 +1,24 @@
1
+ const path = require("node:path");
2
+
3
+ const CSP = "default-src 'self' data: blob:; script-src 'self' 'wasm-unsafe-eval'; worker-src 'self' blob: data:; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'none'";
4
+ const resolveAssetPath = (root, requestURL) => {
5
+ try {
6
+ const url = new URL(requestURL);
7
+ if (url.protocol !== "next2d:" || url.host !== "game" || url.username || url.password) {
8
+ return null;
9
+ }
10
+ const name = decodeURIComponent(url.pathname);
11
+ if (name.includes("\0") || name.includes("\\")) {
12
+ return null;
13
+ }
14
+ const file = path.resolve(root, `.${name === "/" ? "/index.html" : name}`);
15
+ const relative = path.relative(root, file);
16
+ if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
17
+ return null;
18
+ }
19
+ return file;
20
+ } catch {
21
+ return null;
22
+ }
23
+ };
24
+ module.exports = { CSP, resolveAssetPath };
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "next2d-game",
3
+ "version": "1.0.0",
4
+ "description": "Next2D desktop game",
5
+ "private": true,
6
+ "type": "commonjs",
7
+ "main": "index.js",
8
+ "devDependencies": {
9
+ "electron": "44.4.3"
10
+ }
11
+ }
@@ -192,16 +192,18 @@ bool ComputeLayout(const std::string& css_font, const std::wstring& text, Layout
192
192
  stbtt_GetFontVMetrics(&lo.font->info, &lo.ascent, &lo.descent, &line_gap);
193
193
  lo.cps = ToCodepoints(text);
194
194
 
195
+ // Keep layout arithmetic in double; stb's bitmap API still takes float.
196
+ const double scale = lo.scale;
195
197
  double x = 0;
196
198
  for (size_t i = 0; i < lo.cps.size(); ++i) {
197
199
  int advance = 0, lsb = 0;
198
200
  stbtt_GetCodepointHMetrics(&lo.font->info, static_cast<int>(lo.cps[i]), &advance, &lsb);
199
- x += advance * lo.scale;
201
+ x += advance * scale;
200
202
  if (i + 1 < lo.cps.size()) {
201
203
  x += stbtt_GetCodepointKernAdvance(
202
204
  &lo.font->info,
203
205
  static_cast<int>(lo.cps[i]),
204
- static_cast<int>(lo.cps[i + 1])) * lo.scale;
206
+ static_cast<int>(lo.cps[i + 1])) * scale;
205
207
  }
206
208
  }
207
209
  lo.width = x;
@@ -239,9 +241,10 @@ bool MeasureText(const std::string& css_font, const std::wstring& text,
239
241
  if (!ComputeLayout(css_font, text, lo)) {
240
242
  return false;
241
243
  }
244
+ const double scale = lo.scale;
242
245
  out.width = lo.width;
243
- out.ascent = lo.ascent * lo.scale;
244
- out.descent = -lo.descent * lo.scale; // stbtt の descent は負値
246
+ out.ascent = lo.ascent * scale;
247
+ out.descent = -static_cast<double>(lo.descent) * scale; // stbtt の descent は負値
245
248
  return true;
246
249
  }
247
250
 
@@ -256,8 +259,9 @@ bool RasterizeText(const std::string& css_font, const std::wstring& text,
256
259
  return false;
257
260
  }
258
261
 
259
- const double ascent_px = lo.ascent * lo.scale;
260
- const double descent_px = -lo.descent * lo.scale;
262
+ const double scale = lo.scale;
263
+ const double ascent_px = lo.ascent * scale;
264
+ const double descent_px = -static_cast<double>(lo.descent) * scale;
261
265
  const int width = std::max(1, static_cast<int>(std::ceil(lo.width)) + 2);
262
266
  const int height = std::max(1, static_cast<int>(std::ceil(ascent_px + descent_px)) + 2);
263
267
  const int baseline = static_cast<int>(std::ceil(ascent_px)) + 1;
@@ -272,7 +276,7 @@ bool RasterizeText(const std::string& css_font, const std::wstring& text,
272
276
 
273
277
  int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
274
278
  const float shift_x =
275
- static_cast<float>(pen_x + lsb * lo.scale - std::floor(pen_x + lsb * lo.scale));
279
+ static_cast<float>(pen_x + lsb * scale - std::floor(pen_x + lsb * scale));
276
280
  stbtt_GetCodepointBitmapBoxSubpixel(
277
281
  &lo.font->info, cp, lo.scale, lo.scale, shift_x, 0, &x0, &y0, &x1, &y1);
278
282
 
@@ -284,7 +288,7 @@ bool RasterizeText(const std::string& css_font, const std::wstring& text,
284
288
  &lo.font->info, glyph.data(), gw, gh, gw,
285
289
  lo.scale, lo.scale, shift_x, 0, cp);
286
290
 
287
- const int dst_x = static_cast<int>(std::floor(pen_x + lsb * lo.scale));
291
+ const int dst_x = static_cast<int>(std::floor(pen_x + lsb * scale));
288
292
  const int dst_y = baseline + y0;
289
293
  for (int yy = 0; yy < gh; ++yy) {
290
294
  const int py = dst_y + yy;
@@ -298,10 +302,10 @@ bool RasterizeText(const std::string& css_font, const std::wstring& text,
298
302
  }
299
303
  }
300
304
 
301
- pen_x += advance * lo.scale;
305
+ pen_x += advance * scale;
302
306
  if (i + 1 < lo.cps.size()) {
303
307
  pen_x += stbtt_GetCodepointKernAdvance(
304
- &lo.font->info, cp, static_cast<int>(lo.cps[i + 1])) * lo.scale;
308
+ &lo.font->info, cp, static_cast<int>(lo.cps[i + 1])) * scale;
305
309
  }
306
310
  }
307
311
 
@@ -2,7 +2,8 @@
2
2
  # macOS / Linux で Windows 実機なしに実行できる検証をまとめて回す。
3
3
  # 1. RasterCore 単体テスト (実行)
4
4
  # 2. RasterCore 単体テスト (ASan/UBSan)
5
- # 3. V8 依存ソースの構文チェック (実 V8 ヘッダに対して。Windows API 非依存の12ファイル)
5
+ # 3. stb 境界値・デコード回帰テスト (通常 + ASan/UBSan)
6
+ # 4. V8 依存ソースの構文チェック (実 V8 ヘッダに対して)
6
7
  #
7
8
  # 使い方:
8
9
  # tests/check_local.sh [V8_INCLUDE_DIR]
@@ -13,16 +14,23 @@ cd "$(dirname "$0")/.."
13
14
  V8_TAG="13.7.152.19"
14
15
  V8INC="${1:-}"
15
16
 
16
- echo "== 1/3 RasterCore unit tests =="
17
+ echo "== 1/4 RasterCore unit tests =="
17
18
  c++ -std=c++17 -Wall -Wextra -O1 -o /tmp/next2d_raster_test tests/raster_test.cpp
18
19
  /tmp/next2d_raster_test
19
20
 
20
- echo "== 2/3 RasterCore unit tests (ASan/UBSan) =="
21
+ echo "== 2/4 RasterCore unit tests (ASan/UBSan) =="
21
22
  c++ -std=c++17 -g -fsanitize=address,undefined -fno-omit-frame-pointer \
22
23
  -o /tmp/next2d_raster_test_asan tests/raster_test.cpp
23
24
  /tmp/next2d_raster_test_asan
24
25
 
25
- echo "== 3/3 V8-dependent sources syntax check (V8 ${V8_TAG}) =="
26
+ echo "== 3/4 stb safety regression tests =="
27
+ c++ -std=c++17 -O1 -o /tmp/next2d_stb_safety_test tests/stb_safety_test.cpp
28
+ /tmp/next2d_stb_safety_test
29
+ c++ -std=c++17 -g -fsanitize=address,undefined -fno-omit-frame-pointer \
30
+ -o /tmp/next2d_stb_safety_test_asan tests/stb_safety_test.cpp
31
+ /tmp/next2d_stb_safety_test_asan
32
+
33
+ echo "== 4/4 V8-dependent sources syntax check (V8 ${V8_TAG}) =="
26
34
  if [ -z "${V8INC}" ]; then
27
35
  if [ ! -d ".v8_headers/include" ]; then
28
36
  git clone --depth 1 --branch "${V8_TAG}" --filter=blob:none --sparse \
@@ -0,0 +1,126 @@
1
+ // Regression tests for local stb patches. No Windows SDK, V8 or external assets.
2
+ #include <climits>
3
+ #include <cstdio>
4
+ #include <cstdlib>
5
+ #include <vector>
6
+
7
+ // Record allocation attempts without allocating huge buffers in boundary tests.
8
+ static size_t last_allocation = 0;
9
+ static void* TestMalloc(size_t size)
10
+ {
11
+ last_allocation = size;
12
+ return size > 64 * 1024 * 1024 ? nullptr : std::malloc(size);
13
+ }
14
+ static void* TestRealloc(void* data, size_t size)
15
+ {
16
+ last_allocation = size;
17
+ return size > 64 * 1024 * 1024 ? nullptr : std::realloc(data, size);
18
+ }
19
+
20
+ #define STBI_MALLOC TestMalloc
21
+ #define STBI_REALLOC TestRealloc
22
+ #define STBI_FREE std::free
23
+ #define STBI_NO_STDIO
24
+ #define STB_IMAGE_IMPLEMENTATION
25
+ #include "../third_party/stb_image.h"
26
+
27
+ #define STB_VORBIS_NO_STDIO
28
+ #define STB_VORBIS_NO_PUSHDATA_API
29
+ #include "../third_party/stb_vorbis.c"
30
+
31
+ static int failures = 0;
32
+ static int checks = 0;
33
+ #define EXPECT(condition) do { ++checks; if (!(condition)) { \
34
+ ++failures; std::printf("not ok - line %d: %s\n", __LINE__, #condition); } } while (0)
35
+
36
+ static void Test16BitConversion()
37
+ {
38
+ auto* pixel = static_cast<stbi__uint16*>(std::malloc(3 * sizeof(stbi__uint16)));
39
+ pixel[0] = 123; pixel[1] = 456; pixel[2] = 789;
40
+ auto* rgba = stbi__convert_format16(pixel, 3, 4, 1, 1);
41
+ EXPECT(rgba != nullptr);
42
+ if (rgba) {
43
+ EXPECT(rgba[0] == 123 && rgba[1] == 456 && rgba[2] == 789 && rgba[3] == 65535);
44
+ std::free(rgba);
45
+ }
46
+
47
+ // The original unsigned product wraps to zero for these dimensions.
48
+ // Conversion must reject the size before allocating or reading input pixels.
49
+ const unsigned int dimensions[][2] = {{65536, 8192}, {UINT_MAX, 1}, {1, UINT_MAX}};
50
+ for (const auto& size : dimensions) {
51
+ pixel = static_cast<stbi__uint16*>(std::malloc(3 * sizeof(stbi__uint16)));
52
+ last_allocation = 0;
53
+ rgba = stbi__convert_format16(pixel, 3, 4, size[0], size[1]);
54
+ EXPECT(rgba == nullptr);
55
+ EXPECT(last_allocation == 0);
56
+ std::free(rgba);
57
+ }
58
+ }
59
+
60
+ static void TestAnimatedGif()
61
+ {
62
+ // Original fixture: 1x1, black/red palette. Frame 2 restores frame 1;
63
+ // transparent frame 3 must therefore show red, not read before the buffer.
64
+ std::vector<stbi_uc> gif = {
65
+ 'G', 'I', 'F', '8', '9', 'a', 1, 0, 1, 0, 0x80, 0, 0,
66
+ 0, 0, 0, 255, 0, 0
67
+ };
68
+ const stbi_uc controls[] = {4, 12, 5};
69
+ const stbi_uc pixels[] = {0x4c, 0x44, 0x44};
70
+ for (int i = 0; i < 3; ++i) {
71
+ gif.insert(gif.end(), {0x21, 0xf9, 4, controls[i], 1, 0, 0, 0,
72
+ 0x2c, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, pixels[i], 1, 0});
73
+ }
74
+ gif.push_back(0x3b);
75
+ int* delays = nullptr;
76
+ int width = 0, height = 0, frames = 0, components = 0;
77
+ stbi_uc* rgba = stbi_load_gif_from_memory(gif.data(), static_cast<int>(gif.size()),
78
+ &delays, &width, &height, &frames, &components, 4);
79
+ EXPECT(rgba != nullptr);
80
+ EXPECT(width == 1 && height == 1 && frames == 3 && components == 4);
81
+ if (rgba && frames == 3) {
82
+ EXPECT(rgba[0] == 255 && rgba[1] == 0 && rgba[2] == 0 && rgba[3] == 255);
83
+ EXPECT(rgba[4] == 0 && rgba[5] == 0 && rgba[6] == 0 && rgba[7] == 255);
84
+ EXPECT(rgba[8] == 255 && rgba[9] == 0 && rgba[10] == 0 && rgba[11] == 255);
85
+ }
86
+ EXPECT(delays != nullptr);
87
+ if (delays && frames == 3) EXPECT(delays[0] == 10 && delays[1] == 10 && delays[2] == 10);
88
+ stbi_image_free(rgba);
89
+ stbi_image_free(delays);
90
+
91
+ // Oversized canvas is rejected before any pixel buffer is allocated.
92
+ gif[6] = gif[7] = gif[8] = gif[9] = 255;
93
+ last_allocation = 0;
94
+ rgba = stbi_load_gif_from_memory(gif.data(), static_cast<int>(gif.size()),
95
+ &delays, &width, &height, &frames, &components, 4);
96
+ EXPECT(rgba == nullptr);
97
+ EXPECT(last_allocation == 0);
98
+ stbi_image_free(rgba);
99
+ stbi_image_free(delays);
100
+ }
101
+
102
+ static void TestVorbisSeekBounds()
103
+ {
104
+ unsigned char bytes[16] = {};
105
+ stb_vorbis decoder = {};
106
+ decoder.stream_start = bytes;
107
+ decoder.stream_end = bytes + sizeof(bytes);
108
+ for (unsigned int offset : {0U, 15U, 16U, 17U, UINT_MAX, 0U}) {
109
+ const bool valid = offset < sizeof(bytes);
110
+ EXPECT(set_file_offset(&decoder, offset) == static_cast<int>(valid));
111
+ EXPECT(decoder.eof == static_cast<int>(!valid));
112
+ EXPECT(decoder.stream == (valid ? bytes + offset : decoder.stream_end));
113
+ }
114
+ decoder.stream_end = decoder.stream_start;
115
+ EXPECT(set_file_offset(&decoder, 0) == 0);
116
+ EXPECT(decoder.eof == 1 && decoder.stream == decoder.stream_end);
117
+ }
118
+
119
+ int main()
120
+ {
121
+ Test16BitConversion();
122
+ TestAnimatedGif();
123
+ TestVorbisSeekBounds();
124
+ std::printf("stb safety: %d checks, %d failures\n", checks, failures);
125
+ return failures ? 1 : 0;
126
+ }
@@ -12,3 +12,18 @@
12
12
  | dr_wav.h | https://github.com/mackron/dr_libs | public domain (Unlicense) / MIT-0 のデュアル |
13
13
 
14
14
  いずれも各ファイル末尾に原文のライセンス条文が含まれる。
15
+
16
+ ## ローカル修正 / Local patches
17
+
18
+ 同梱の stb には以下の修正を適用している。更新時は再適用の要否を確認し、
19
+ `tests/stb_safety_test.cpp` と既存のプラットフォームテストを実行する。
20
+
21
+ The vendored stb files include these local patches. When updating upstream code,
22
+ check whether they are still needed and run `tests/stb_safety_test.cpp` and the
23
+ existing platform tests.
24
+
25
+ | ファイル / File | 修正 / Patch |
26
+ |---|---|
27
+ | stb_image.h | メモリサイズを乗算前に拡張し、16bit変換・GIFフレーム追加時のオーバーフローを検証。GIFの過去フレーム参照をバッファ内に修正。 / Widen operands before size multiplication, validate overflow in 16-bit conversion and GIF frame growth, and keep GIF previous-frame references inside the buffer. |
28
+ | stb_truetype.h | ビットマップの確保・初期化サイズを `size_t` で乗算。 / Multiply bitmap allocation and initialization sizes in `size_t`. |
29
+ | stb_vorbis.c | ポインタ加算前に入力サイズとシーク位置を比較。 / Check seek offsets against the input size before pointer arithmetic. |