@wasm-gaming/engine-specs 0.2.7 → 0.3.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 (48) hide show
  1. package/README.md +63 -54
  2. package/demo/app.html +227 -0
  3. package/demo/canvas-renderer.js +153 -0
  4. package/demo/components/asset-picker.html +137 -0
  5. package/demo/components/call-log.html +33 -0
  6. package/demo/components/engine-header.html +78 -0
  7. package/demo/components/lifecycle-bar.html +69 -0
  8. package/demo/components/property-table.html +119 -0
  9. package/demo/demo.css +26 -130
  10. package/demo/demo.sdk.js +405 -203
  11. package/demo/index.html +7 -56
  12. package/demo/tsconfig.json +17 -0
  13. package/dist/engine-specs.d.ts +99 -0
  14. package/dist/engine-specs.d.ts.map +1 -0
  15. package/dist/engine-specs.js +3 -0
  16. package/dist/engine-specs.js.map +1 -0
  17. package/package.json +7 -12
  18. package/src/engine-specs.ts +110 -0
  19. package/demo/README.md +0 -155
  20. package/demo/checksums.js +0 -101
  21. package/demo/components/drop-area.html +0 -171
  22. package/demo/components/esc-menu.html +0 -540
  23. package/demo/components/launcher.html +0 -220
  24. package/demo/components/sdk-info.html +0 -54
  25. package/demo/demo.js +0 -622
  26. package/demo/sdk.js +0 -19
  27. package/demo/storage.js +0 -88
  28. package/dist/engine.d.ts +0 -427
  29. package/dist/engine.d.ts.map +0 -1
  30. package/dist/engine.js +0 -83
  31. package/dist/engine.js.map +0 -1
  32. package/dist/index.d.ts +0 -13
  33. package/dist/index.d.ts.map +0 -1
  34. package/dist/index.js +0 -12
  35. package/dist/index.js.map +0 -1
  36. package/dist/manifest.d.ts +0 -69
  37. package/dist/manifest.d.ts.map +0 -1
  38. package/dist/manifest.js +0 -8
  39. package/dist/manifest.js.map +0 -1
  40. package/dist/validate.d.ts +0 -10
  41. package/dist/validate.d.ts.map +0 -1
  42. package/dist/validate.js +0 -79
  43. package/dist/validate.js.map +0 -1
  44. package/schema/engine-manifest.schema.json +0 -74
  45. package/src/engine.ts +0 -516
  46. package/src/index.ts +0 -55
  47. package/src/manifest.ts +0 -82
  48. package/src/validate.ts +0 -92
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "declaration": false,
6
+ "declarationMap": false,
7
+ "sourceMap": false,
8
+ "rootDir": "..",
9
+ "allowJs": true,
10
+ "checkJs": true
11
+ },
12
+ "include": [
13
+ "../src",
14
+ "demo.sdk.js",
15
+ "canvas-renderer.js"
16
+ ]
17
+ }
@@ -0,0 +1,99 @@
1
+ export interface PropertyDefinition<Value = unknown> {
2
+ value: Value;
3
+ default?: Value;
4
+ description?: string;
5
+ live?: boolean;
6
+ enum?: readonly Value[];
7
+ allowedTypes?: readonly string[];
8
+ }
9
+ export type PropertiesDefinition<T> = {
10
+ [K in keyof T]-?: PropertyDefinition<T[K]>;
11
+ };
12
+ export interface EngineConfig {
13
+ [key: string]: unknown;
14
+ }
15
+ export interface EngineOptions {
16
+ [key: string]: unknown;
17
+ }
18
+ export interface EngineAssets {
19
+ [key: string]: unknown;
20
+ }
21
+ export interface EngineStorage {
22
+ [key: string]: unknown;
23
+ }
24
+ /**
25
+ * Members are `object`, not the four interfaces above, on purpose: a named
26
+ * `interface MyEngineConfig { … }` has no implicit index signature, so
27
+ * constraining to a type that has one would reject exactly what a closed-type
28
+ * engine will write. Each engine can close its own config/options/assets/storage
29
+ * shapes without an index signature and still satisfy this constraint.
30
+ */
31
+ export interface EnginePayloads {
32
+ config: object;
33
+ options: object;
34
+ assets: object;
35
+ storage: object;
36
+ }
37
+ export interface DefaultPayloads extends EnginePayloads {
38
+ config: EngineConfig;
39
+ options: EngineOptions;
40
+ assets: EngineAssets;
41
+ storage: EngineStorage;
42
+ }
43
+ export interface EngineInit<P extends EnginePayloads = DefaultPayloads> {
44
+ config?: Partial<P['config']>;
45
+ options?: Partial<P['options']>;
46
+ assets?: Partial<P['assets']>;
47
+ storage?: Partial<P['storage']>;
48
+ /**
49
+ * Not a `Partial`, unlike the four above: the payloads are patches merged onto a
50
+ * cumulative state, a mount target is a single value that replaces.
51
+ */
52
+ mount?: HTMLElement;
53
+ }
54
+ export interface EngineSurface<TSelf, P extends EnginePayloads = DefaultPayloads> {
55
+ config(): Promise<PropertiesDefinition<P['config']>>;
56
+ config(patch: Partial<P['config']>): TSelf;
57
+ options(): Promise<PropertiesDefinition<P['options']>>;
58
+ options(patch: Partial<P['options']>): TSelf;
59
+ assets(): Promise<PropertiesDefinition<P['assets']>>;
60
+ assets(patch: Partial<P['assets']>): TSelf;
61
+ storage(): Promise<PropertiesDefinition<P['storage']>>;
62
+ storage(patch: Partial<P['storage']>): TSelf;
63
+ mount(target: HTMLElement): TSelf;
64
+ }
65
+ export interface EnginePlay<P extends EnginePayloads = DefaultPayloads> extends EngineSurface<PromisePlay<P>, P> {
66
+ restart(options?: Partial<P['options']>): PromisePlay<P>;
67
+ destroy(): Promise<void>;
68
+ }
69
+ /**
70
+ * Structural, not nominal: an engine does NOT need to subclass the real
71
+ * `Promise` to satisfy this. Implementing `then`/`catch`/`finally` with the
72
+ * right signatures — typically by delegating to an internal real `Promise` —
73
+ * is enough. Note that `extends Promise<T>` also pulls in
74
+ * `[Symbol.toStringTag]: string` via lib.es2015.symbol.wellknown, so an
75
+ * implementer needs that member too, not just the three methods.
76
+ */
77
+ export interface PromisePlay<P extends EnginePayloads = DefaultPayloads> extends Promise<EnginePlay<P>>, EngineSurface<PromisePlay<P>, P> {
78
+ restart(options?: Partial<P['options']>): PromisePlay<P>;
79
+ destroy(): Promise<void>;
80
+ }
81
+ export interface EngineSDK<P extends EnginePayloads = DefaultPayloads> extends EngineSurface<EngineSDK<P>, P> {
82
+ boot(init?: EngineInit<P>): PromisePlay<P>;
83
+ start(options?: Partial<P['options']>): PromisePlay<P>;
84
+ }
85
+ /**
86
+ * Identity lives on the constructor, not on an instance: a launcher listing engines
87
+ * reads `Rsdkv4SDK.id` from the imported class, without constructing anything.
88
+ */
89
+ export interface EngineSDKConstructor<P extends EnginePayloads = DefaultPayloads> {
90
+ /** Stable engine id, e.g. "rsdkv4" | "fbneo" | "blastem" | "jgenesis" | "smw". */
91
+ readonly id: string;
92
+ /** SemVer of this engine build/contract implementation. */
93
+ readonly version: string;
94
+ /** Human-readable name. */
95
+ readonly name?: string;
96
+ readonly description?: string;
97
+ new (init?: EngineInit<P>): EngineSDK<P>;
98
+ }
99
+ //# sourceMappingURL=engine-specs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-specs.d.ts","sourceRoot":"","sources":["../src/engine-specs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ // ---- Introspectable property definitions ----
2
+ export {};
3
+ //# sourceMappingURL=engine-specs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-specs.js","sourceRoot":"","sources":["../src/engine-specs.ts"],"names":[],"mappings":"AAAA,gDAAgD"}
package/package.json CHANGED
@@ -1,21 +1,19 @@
1
1
  {
2
2
  "name": "@wasm-gaming/engine-specs",
3
- "version": "0.2.7",
4
- "description": "The wasm-gaming engine contract: the single shared interface every WASM game/emulator engine and the host app conform to. TypeScript types + JSON Schema + a Zod runtime validator.",
3
+ "version": "0.3.0",
4
+ "description": "The wasm-gaming engine contract: the single shared JS API every WASM game/emulator engine and the host app conform to. TypeScript types, no runtime dependencies.",
5
5
  "type": "module",
6
- "types": "./dist/index.d.ts",
6
+ "types": "./dist/engine-specs.d.ts",
7
7
  "exports": {
8
8
  ".": {
9
- "types": "./dist/index.d.ts",
10
- "import": "./dist/index.js"
11
- },
12
- "./schema/engine-manifest.json": "./schema/engine-manifest.schema.json"
9
+ "types": "./dist/engine-specs.d.ts",
10
+ "import": "./dist/engine-specs.js"
11
+ }
13
12
  },
14
13
  "files": [
15
14
  "dist/",
16
15
  "demo/",
17
- "src/",
18
- "schema/"
16
+ "src/"
19
17
  ],
20
18
  "publishConfig": {
21
19
  "access": "public",
@@ -30,8 +28,5 @@
30
28
  "repository": {
31
29
  "type": "git",
32
30
  "url": "git+https://github.com/wasm-gaming/engine-specs.git"
33
- },
34
- "dependencies": {
35
- "zod": "^4.4.3"
36
31
  }
37
32
  }
@@ -0,0 +1,110 @@
1
+ // ---- Introspectable property definitions ----
2
+
3
+ export interface PropertyDefinition<Value = unknown> {
4
+ value: Value;
5
+ default?: Value;
6
+ description?: string;
7
+ live?: boolean; // Absent means `false`. `true` = changing it applies to current play session.
8
+ enum?: readonly Value[];
9
+ allowedTypes?: readonly string[];
10
+ }
11
+
12
+ export type PropertiesDefinition<T> = { [K in keyof T]-?: PropertyDefinition<T[K]> };
13
+
14
+ // ---- Default open payloads, for an engine that doesn't close its types ----
15
+
16
+ export interface EngineConfig { [key: string]: unknown }
17
+ export interface EngineOptions { [key: string]: unknown }
18
+ export interface EngineAssets { [key: string]: unknown }
19
+ export interface EngineStorage { [key: string]: unknown }
20
+
21
+ /**
22
+ * Members are `object`, not the four interfaces above, on purpose: a named
23
+ * `interface MyEngineConfig { … }` has no implicit index signature, so
24
+ * constraining to a type that has one would reject exactly what a closed-type
25
+ * engine will write. Each engine can close its own config/options/assets/storage
26
+ * shapes without an index signature and still satisfy this constraint.
27
+ */
28
+ export interface EnginePayloads {
29
+ config: object;
30
+ options: object;
31
+ assets: object;
32
+ storage: object;
33
+ }
34
+
35
+ export interface DefaultPayloads extends EnginePayloads {
36
+ config: EngineConfig;
37
+ options: EngineOptions;
38
+ assets: EngineAssets;
39
+ storage: EngineStorage;
40
+ }
41
+
42
+ export interface EngineInit<P extends EnginePayloads = DefaultPayloads> {
43
+ config?: Partial<P['config']>;
44
+ options?: Partial<P['options']>;
45
+ assets?: Partial<P['assets']>;
46
+ storage?: Partial<P['storage']>;
47
+ /**
48
+ * Not a `Partial`, unlike the four above: the payloads are patches merged onto a
49
+ * cumulative state, a mount target is a single value that replaces.
50
+ */
51
+ mount?: HTMLElement;
52
+ }
53
+
54
+ // ---- The shared fluent surface: the members every carrier exposes ----
55
+
56
+ export interface EngineSurface<TSelf, P extends EnginePayloads = DefaultPayloads> {
57
+ config(): Promise<PropertiesDefinition<P['config']>>;
58
+ config(patch: Partial<P['config']>): TSelf;
59
+ options(): Promise<PropertiesDefinition<P['options']>>;
60
+ options(patch: Partial<P['options']>): TSelf;
61
+ assets(): Promise<PropertiesDefinition<P['assets']>>;
62
+ assets(patch: Partial<P['assets']>): TSelf;
63
+ storage(): Promise<PropertiesDefinition<P['storage']>>;
64
+ storage(patch: Partial<P['storage']>): TSelf;
65
+ mount(target: HTMLElement): TSelf;
66
+ }
67
+
68
+ // ---- Public contract each WASM engine must implement ----
69
+
70
+ export interface EnginePlay<P extends EnginePayloads = DefaultPayloads>
71
+ extends EngineSurface<PromisePlay<P>, P> {
72
+ restart(options?: Partial<P['options']>): PromisePlay<P>;
73
+ destroy(): Promise<void>;
74
+ }
75
+
76
+ /**
77
+ * Structural, not nominal: an engine does NOT need to subclass the real
78
+ * `Promise` to satisfy this. Implementing `then`/`catch`/`finally` with the
79
+ * right signatures — typically by delegating to an internal real `Promise` —
80
+ * is enough. Note that `extends Promise<T>` also pulls in
81
+ * `[Symbol.toStringTag]: string` via lib.es2015.symbol.wellknown, so an
82
+ * implementer needs that member too, not just the three methods.
83
+ */
84
+ export interface PromisePlay<P extends EnginePayloads = DefaultPayloads>
85
+ extends Promise<EnginePlay<P>>, EngineSurface<PromisePlay<P>, P> {
86
+ restart(options?: Partial<P['options']>): PromisePlay<P>;
87
+ destroy(): Promise<void>;
88
+ }
89
+
90
+ export interface EngineSDK<P extends EnginePayloads = DefaultPayloads>
91
+ extends EngineSurface<EngineSDK<P>, P> {
92
+ boot(init?: EngineInit<P>): PromisePlay<P>;
93
+ start(options?: Partial<P['options']>): PromisePlay<P>;
94
+ }
95
+
96
+ /**
97
+ * Identity lives on the constructor, not on an instance: a launcher listing engines
98
+ * reads `Rsdkv4SDK.id` from the imported class, without constructing anything.
99
+ */
100
+ export interface EngineSDKConstructor<P extends EnginePayloads = DefaultPayloads> {
101
+ /** Stable engine id, e.g. "rsdkv4" | "fbneo" | "blastem" | "jgenesis" | "smw". */
102
+ readonly id: string;
103
+ /** SemVer of this engine build/contract implementation. */
104
+ readonly version: string;
105
+ /** Human-readable name. */
106
+ readonly name?: string;
107
+ readonly description?: string;
108
+
109
+ new (init?: EngineInit<P>): EngineSDK<P>;
110
+ }
package/demo/README.md DELETED
@@ -1,155 +0,0 @@
1
- # Themable Engine Demo Template
2
-
3
- This directory contains a standalone, themable web interface template for hosting interactive WASM gaming engine demos.
4
-
5
- ## 🚀 How to use in another engine repository
6
-
7
- 1. **Copy the `demo/` folder** into your engine repository.
8
-
9
- 2. **Initialize via ES module script**:
10
- ```html
11
- <script type="module">
12
- import demo from './demo.js'
13
- import sdk from './demo.sdk.js' // Or your engine SDK import
14
-
15
- demo
16
- .init({
17
- messages: {
18
- title: 'My Custom Engine Demo',
19
- },
20
- bios: false, // 'required' | 'optional' | false
21
- escMenu: {
22
- enabled: true,
23
- title: 'Game Pause Menu',
24
- items: [
25
- { label: 'Toggle Fullscreen', action: (instance) => { document.documentElement.requestFullscreen?.(); } }
26
- ],
27
- },
28
- })
29
- .on('file', ({ type, action, file }) => {
30
- if (file) {
31
- console.log(`[Demo] Selected ${type} file: ${file.name}`)
32
- }
33
- })
34
- .on('launch', async (e) => {
35
- const assets = {}
36
-
37
- // if rom loaded
38
- if (demo.hasFile('rom')) {
39
- assets.rom = await demo.loadToMEMFS('rom')
40
- }
41
-
42
- // if bios required and loaded
43
- if (demo.hasFile('bios')) {
44
- assets.bios = await demo.loadToMEMFS('bios')
45
- }
46
-
47
- const instance = await sdk.load({
48
- attachTo: e.attachTo,
49
- assets,
50
- options: { fileName: e.fileName, byteLength: e.byteLength },
51
- })
52
-
53
- instance.start()
54
- return instance
55
- })
56
- </script>
57
- ```
58
-
59
- ---
60
-
61
- ## 🎮 Component79 ESC Menu
62
-
63
- The demo includes an interactive, customizable in-game ESC pause menu component built with **Component79**.
64
-
65
- ### ESC Menu Options (`escMenu`)
66
-
67
- | Option | Type | Default | Description |
68
- |---|---|---|---|
69
- | `enabled` | `boolean` | `true` | Enable/disable in-game ESC menu overlay. |
70
- | `title` | `string` | `"Game Menu"` | Title displayed at top of ESC menu modal. |
71
- | `shortcutKey` | `string` | `"Escape"` | Keyboard key that toggles the menu overlay. |
72
- | `showTriggerButton` | `boolean` | `true` | Display floating top-right "ESC Menu" trigger badge for mouse/touch access. |
73
- | `items` | `Array<{label: string, action: function}>` | `[]` | Custom action buttons added to ESC menu. |
74
- | `onReset` | `function` | `instance.reset()` | Callback when Reset Game button is pressed. |
75
- | `onExit` | `function` | `instance.destroy()` | Callback when Exit to Launcher button is pressed. |
76
- | `onSaveState` | `function` | `instance.saveState()` | Callback when Save State button is pressed. |
77
- | `onLoadState` | `function` | `instance.loadState()` | Callback when Load State button is pressed. |
78
-
79
- ---
80
-
81
- ## 🎨 Customizing & Theming
82
-
83
- The demo visual design system is fully controlled via **CSS Custom Properties** defined in `demo.css`.
84
-
85
- ### Overriding CSS Variables
86
-
87
- To apply your custom branding, override any of the `:root` variables in `demo.css` or in your own custom stylesheet:
88
-
89
- ```css
90
- :root {
91
- /* Brand colors */
92
- --demo-bg-a: #1e1b4b;
93
- --demo-bg-b: #0f172a;
94
- --demo-text: #f8fafc;
95
- --demo-muted: #94a3b8;
96
-
97
- /* Accent highlights */
98
- --demo-accent: #00ffcc;
99
- --demo-accent-rgb: 0, 255, 204;
100
- --demo-accent2: #38bdf8;
101
- --demo-accent2-rgb: 56, 189, 248;
102
-
103
- /* Glassmorphism panels */
104
- --demo-panel-bg: rgba(15, 23, 42, 0.75);
105
- --demo-panel-border: rgba(255, 255, 255, 0.1);
106
- --demo-subpanel-bg: rgba(30, 41, 59, 0.6);
107
- --demo-subpanel-border: rgba(255, 255, 255, 0.1);
108
-
109
- /* ESC Menu styling */
110
- --demo-esc-bg: rgba(6, 9, 16, 0.85);
111
- --demo-esc-card-bg: rgba(15, 23, 42, 0.9);
112
- --demo-esc-text: #f8fafc;
113
- }
114
- ```
115
-
116
- ### Full Token Reference
117
-
118
- | Variable | Description | Default |
119
- |---|---|---|
120
- | `--demo-bg-a` | Gradient background stop 1 | `#d8b4fe` |
121
- | `--demo-bg-b` | Gradient background stop 2 | `#7dd3fc` |
122
- | `--demo-text` | Primary text color | `#0f172a` |
123
- | `--demo-muted` | Secondary/muted text color | `#475569` |
124
- | `--demo-accent` | Primary accent color | `#ff007f` |
125
- | `--demo-accent-rgb` | Primary accent RGB channels | `255, 0, 127` |
126
- | `--demo-accent2` | Secondary accent color | `#0284c7` |
127
- | `--demo-accent2-rgb` | Secondary accent RGB channels | `2, 132, 199` |
128
- | `--demo-panel-bg` | Main card/launcher background | `rgba(255, 255, 255, 0.25)` |
129
- | `--demo-panel-border` | Main card border color | `rgba(255, 255, 255, 0.4)` |
130
- | `--demo-panel-radius` | Main card corner rounding | `12px` |
131
- | `--demo-panel-blur` | Backdrop glass blur effect | `24px` |
132
- | `--demo-subpanel-bg` | Inner info section background | `rgba(255, 255, 255, 0.35)` |
133
- | `--demo-drop-bg` | Dropzone background | `rgba(255, 255, 255, 0.15)` |
134
- | `--demo-drop-active-bg` | Drag-over active background | `rgba(255, 255, 255, 0.4)` |
135
- | `--demo-picker-bg` | Pick File button background | `rgba(255, 255, 255, 0.5)` |
136
- | `--demo-pill-bg` | Metadata badge background | `rgba(var(--demo-accent-rgb), 0.1)` |
137
- | `--demo-file-info-bg` | Canvas overlay panel background | `rgba(255, 255, 255, 0.7)` |
138
- | `--demo-esc-bg` | ESC menu overlay background | `rgba(6, 9, 16, 0.82)` |
139
- | `--demo-esc-card-bg` | ESC menu card background | `rgba(15, 23, 42, 0.88)` |
140
- | `--demo-esc-text` | ESC menu text color | `#f8fafc` |
141
- | `--demo-font-sans` | Primary sans-serif font family | `"Outfit", system-ui, sans-serif` |
142
- | `--demo-font-mono` | Monospace code font family | `"Space Mono", monospace` |
143
-
144
- ---
145
-
146
- ## 🛠 Features
147
-
148
- - **Component79 ESC Menu**: Customizable pause menu overlay with keydown listeners and touch/mouse badges.
149
- - **OPFS Asset Persistence**: Automatically stores selected ROM and BIOS files in the Origin Private File System (`navigator.storage.getDirectory()`), restoring them automatically across page reloads.
150
- - **Split Asset Drop Zones**: Independent drop/picker areas for optional **BIOS** firmware and game **ROM** assets.
151
- - **Dedicated Launch Trigger**: Prominent launch button that activates once required game assets are selected.
152
- - **Automated Manifest Integration**: Page title, SDK info, asset key mapping, and file type (`accept`) filters are automatically loaded from `sdk.manifest`.
153
- - **Checksum Calculation**: Computes MD5, SHA-1, and SHA-256 hashes of loaded files asynchronously in the background.
154
- - **Shadow DOM Isolation**: UI components are isolated using Shadow DOM to prevent style leakage.
155
- - **Subpath Hosting Ready**: Uses `import.meta.url` for component templates to work seamlessly on subpaths (e.g. GitHub Pages).
package/demo/checksums.js DELETED
@@ -1,101 +0,0 @@
1
- const CRC_TABLE = Uint32Array.from({ length: 256 }, (_, n) => {
2
- let c = n;
3
- for (let k = 0; k < 8; k += 1) {
4
- c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
5
- }
6
- return c >>> 0;
7
- });
8
-
9
- const crc32 = (bytes) => {
10
- let crc = 0xffffffff;
11
- for (const byte of bytes) {
12
- crc = CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
13
- }
14
- return ((crc ^ 0xffffffff) >>> 0).toString(16).padStart(8, "0");
15
- };
16
-
17
- const toHex = (buffer) =>
18
- Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
19
-
20
- // RFC 1321. Hand-rolled because crypto.subtle does not offer MD5.
21
- const MD5_K = Uint32Array.from({ length: 64 }, (_, i) =>
22
- Math.floor(Math.abs(Math.sin(i + 1)) * 2 ** 32));
23
-
24
- const MD5_S = [
25
- 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
26
- 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
27
- 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
28
- 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
29
- ];
30
-
31
- const md5 = (bytes) => {
32
- const paddedLength = (((bytes.length + 8) >> 6) + 1) << 6;
33
- const padded = new Uint8Array(paddedLength);
34
- padded.set(bytes);
35
- padded[bytes.length] = 0x80;
36
- const view = new DataView(padded.buffer);
37
- view.setUint32(paddedLength - 8, (bytes.length * 8) >>> 0, true);
38
- view.setUint32(paddedLength - 4, Math.floor(bytes.length / 0x20000000), true);
39
-
40
- let [h0, h1, h2, h3] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476];
41
- const words = new Uint32Array(16);
42
-
43
- for (let offset = 0; offset < paddedLength; offset += 64) {
44
- for (let j = 0; j < 16; j += 1) {
45
- words[j] = view.getUint32(offset + j * 4, true);
46
- }
47
- let [a, b, c, d] = [h0, h1, h2, h3];
48
- for (let i = 0; i < 64; i += 1) {
49
- let f;
50
- let g;
51
- if (i < 16) {
52
- f = (b & c) | (~b & d);
53
- g = i;
54
- } else if (i < 32) {
55
- f = (d & b) | (~d & c);
56
- g = (5 * i + 1) % 16;
57
- } else if (i < 48) {
58
- f = b ^ c ^ d;
59
- g = (3 * i + 5) % 16;
60
- } else {
61
- f = c ^ (b | ~d);
62
- g = (7 * i) % 16;
63
- }
64
- const sum = (a + f + MD5_K[i] + words[g]) >>> 0;
65
- const rotated = (sum << MD5_S[i]) | (sum >>> (32 - MD5_S[i]));
66
- [a, b, c, d] = [d, (b + rotated) >>> 0, b, c];
67
- }
68
- h0 = (h0 + a) >>> 0;
69
- h1 = (h1 + b) >>> 0;
70
- h2 = (h2 + c) >>> 0;
71
- h3 = (h3 + d) >>> 0;
72
- }
73
-
74
- const digest = new DataView(new ArrayBuffer(16));
75
- digest.setUint32(0, h0, true);
76
- digest.setUint32(4, h1, true);
77
- digest.setUint32(8, h2, true);
78
- digest.setUint32(12, h3, true);
79
- return toHex(digest.buffer);
80
- };
81
-
82
- const sha = async (algorithm, buffer) => {
83
- // crypto.subtle only exists in secure contexts (https or localhost).
84
- if (!globalThis.crypto?.subtle) {
85
- return "unavailable (requires a secure context)";
86
- }
87
- return toHex(await crypto.subtle.digest(algorithm, buffer));
88
- };
89
-
90
- export const checksumAlgorithms = ["CRC32", "MD5", "SHA-1", "SHA-256"];
91
-
92
- export async function computeChecksums(buffer) {
93
- const bytes = new Uint8Array(buffer);
94
- const [sha1, sha256] = await Promise.all([sha("SHA-1", buffer), sha("SHA-256", buffer)]);
95
- return [
96
- { label: "CRC32", value: crc32(bytes) },
97
- { label: "MD5", value: md5(bytes) },
98
- { label: "SHA-1", value: sha1 },
99
- { label: "SHA-256", value: sha256 },
100
- ];
101
- }