esp32tool 1.0.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 (115) hide show
  1. package/README.md +31 -0
  2. package/css/dark.css +156 -0
  3. package/css/light.css +156 -0
  4. package/css/style.css +870 -0
  5. package/dist/const.d.ts +277 -0
  6. package/dist/const.js +511 -0
  7. package/dist/esp_loader.d.ts +222 -0
  8. package/dist/esp_loader.js +1466 -0
  9. package/dist/index.d.ts +10 -0
  10. package/dist/index.js +15 -0
  11. package/dist/lib/spiffs/index.d.ts +15 -0
  12. package/dist/lib/spiffs/index.js +16 -0
  13. package/dist/lib/spiffs/spiffs.d.ts +26 -0
  14. package/dist/lib/spiffs/spiffs.js +132 -0
  15. package/dist/lib/spiffs/spiffsBlock.d.ts +36 -0
  16. package/dist/lib/spiffs/spiffsBlock.js +140 -0
  17. package/dist/lib/spiffs/spiffsConfig.d.ts +63 -0
  18. package/dist/lib/spiffs/spiffsConfig.js +79 -0
  19. package/dist/lib/spiffs/spiffsPage.d.ts +45 -0
  20. package/dist/lib/spiffs/spiffsPage.js +260 -0
  21. package/dist/lib/spiffs/spiffsReader.d.ts +19 -0
  22. package/dist/lib/spiffs/spiffsReader.js +192 -0
  23. package/dist/partition.d.ts +26 -0
  24. package/dist/partition.js +129 -0
  25. package/dist/struct.d.ts +2 -0
  26. package/dist/struct.js +91 -0
  27. package/dist/stubs/esp32.json +8 -0
  28. package/dist/stubs/esp32c2.json +8 -0
  29. package/dist/stubs/esp32c3.json +8 -0
  30. package/dist/stubs/esp32c5.json +8 -0
  31. package/dist/stubs/esp32c6.json +8 -0
  32. package/dist/stubs/esp32c61.json +8 -0
  33. package/dist/stubs/esp32h2.json +8 -0
  34. package/dist/stubs/esp32p4.json +8 -0
  35. package/dist/stubs/esp32p4r3.json +8 -0
  36. package/dist/stubs/esp32s2.json +8 -0
  37. package/dist/stubs/esp32s3.json +8 -0
  38. package/dist/stubs/esp8266.json +8 -0
  39. package/dist/stubs/index.d.ts +10 -0
  40. package/dist/stubs/index.js +56 -0
  41. package/dist/util.d.ts +14 -0
  42. package/dist/util.js +46 -0
  43. package/dist/wasm/filesystems.d.ts +33 -0
  44. package/dist/wasm/filesystems.js +114 -0
  45. package/dist/web/esp32-D955RjN9.js +16 -0
  46. package/dist/web/esp32c2-CJkxHDQi.js +16 -0
  47. package/dist/web/esp32c3-BhUHzH0o.js +16 -0
  48. package/dist/web/esp32c5-Chs0HtmA.js +16 -0
  49. package/dist/web/esp32c6-D6mPN6ut.js +16 -0
  50. package/dist/web/esp32c61-CQiYCWAs.js +16 -0
  51. package/dist/web/esp32h2-LsKJE9AS.js +16 -0
  52. package/dist/web/esp32p4-7nWC-HiD.js +16 -0
  53. package/dist/web/esp32p4r3-CwiPecZW.js +16 -0
  54. package/dist/web/esp32s2-CtqVheSJ.js +16 -0
  55. package/dist/web/esp32s3-CRbtB0QR.js +16 -0
  56. package/dist/web/esp8266-nEkNAo8K.js +16 -0
  57. package/dist/web/index.js +7265 -0
  58. package/electron/main.js +333 -0
  59. package/electron/preload.js +37 -0
  60. package/eslint.config.js +22 -0
  61. package/index.html +408 -0
  62. package/js/modules/esp32-D955RjN9.js +16 -0
  63. package/js/modules/esp32c2-CJkxHDQi.js +16 -0
  64. package/js/modules/esp32c3-BhUHzH0o.js +16 -0
  65. package/js/modules/esp32c5-Chs0HtmA.js +16 -0
  66. package/js/modules/esp32c6-D6mPN6ut.js +16 -0
  67. package/js/modules/esp32c61-CQiYCWAs.js +16 -0
  68. package/js/modules/esp32h2-LsKJE9AS.js +16 -0
  69. package/js/modules/esp32p4-7nWC-HiD.js +16 -0
  70. package/js/modules/esp32p4r3-CwiPecZW.js +16 -0
  71. package/js/modules/esp32s2-CtqVheSJ.js +16 -0
  72. package/js/modules/esp32s3-CRbtB0QR.js +16 -0
  73. package/js/modules/esp8266-nEkNAo8K.js +16 -0
  74. package/js/modules/esptool.js +7265 -0
  75. package/js/script.js +2237 -0
  76. package/js/utilities.js +182 -0
  77. package/license.md +11 -0
  78. package/package.json +61 -0
  79. package/script/build +12 -0
  80. package/script/develop +17 -0
  81. package/src/const.ts +599 -0
  82. package/src/esp_loader.ts +1907 -0
  83. package/src/index.ts +63 -0
  84. package/src/lib/spiffs/index.ts +22 -0
  85. package/src/lib/spiffs/spiffs.ts +175 -0
  86. package/src/lib/spiffs/spiffsBlock.ts +204 -0
  87. package/src/lib/spiffs/spiffsConfig.ts +140 -0
  88. package/src/lib/spiffs/spiffsPage.ts +357 -0
  89. package/src/lib/spiffs/spiffsReader.ts +280 -0
  90. package/src/partition.ts +155 -0
  91. package/src/struct.ts +108 -0
  92. package/src/stubs/README.md +3 -0
  93. package/src/stubs/esp32.json +8 -0
  94. package/src/stubs/esp32c2.json +8 -0
  95. package/src/stubs/esp32c3.json +8 -0
  96. package/src/stubs/esp32c5.json +8 -0
  97. package/src/stubs/esp32c6.json +8 -0
  98. package/src/stubs/esp32c61.json +8 -0
  99. package/src/stubs/esp32h2.json +8 -0
  100. package/src/stubs/esp32p4.json +8 -0
  101. package/src/stubs/esp32p4r3.json +8 -0
  102. package/src/stubs/esp32s2.json +8 -0
  103. package/src/stubs/esp32s3.json +8 -0
  104. package/src/stubs/esp8266.json +8 -0
  105. package/src/stubs/index.ts +86 -0
  106. package/src/util.ts +49 -0
  107. package/src/wasm/fatfs/fatfs.wasm +0 -0
  108. package/src/wasm/fatfs/index.d.ts +26 -0
  109. package/src/wasm/fatfs/index.js +343 -0
  110. package/src/wasm/filesystems.ts +156 -0
  111. package/src/wasm/littlefs/index.d.ts +83 -0
  112. package/src/wasm/littlefs/index.js +529 -0
  113. package/src/wasm/littlefs/littlefs.js +2 -0
  114. package/src/wasm/littlefs/littlefs.wasm +0 -0
  115. package/src/wasm/shared/types.ts +13 -0
@@ -0,0 +1,86 @@
1
+ import {
2
+ ChipFamily,
3
+ CHIP_FAMILY_ESP32,
4
+ CHIP_FAMILY_ESP32S2,
5
+ CHIP_FAMILY_ESP32S3,
6
+ CHIP_FAMILY_ESP8266,
7
+ CHIP_FAMILY_ESP32C2,
8
+ CHIP_FAMILY_ESP32C3,
9
+ CHIP_FAMILY_ESP32C5,
10
+ CHIP_FAMILY_ESP32C6,
11
+ CHIP_FAMILY_ESP32C61,
12
+ CHIP_FAMILY_ESP32H2,
13
+ CHIP_FAMILY_ESP32H4,
14
+ CHIP_FAMILY_ESP32H21,
15
+ CHIP_FAMILY_ESP32P4,
16
+ CHIP_FAMILY_ESP32S31
17
+ } from "../const";
18
+ import { toByteArray } from "../util";
19
+
20
+ interface LoadedStub {
21
+ text: string;
22
+ data: string;
23
+ text_start: number;
24
+ entry: number;
25
+ data_start: number;
26
+ }
27
+
28
+ interface Stub {
29
+ text: number[];
30
+ data: number[];
31
+ text_start: number;
32
+ entry: number;
33
+ data_start: number;
34
+ }
35
+
36
+ export const getStubCode = async (
37
+ chipFamily: ChipFamily,
38
+ chipRevision?: number | null,
39
+ ): Promise<Stub | null> => {
40
+ let stubcode!: LoadedStub;
41
+
42
+ // Chips without stub support yet
43
+ if (
44
+ chipFamily == CHIP_FAMILY_ESP32H4 ||
45
+ chipFamily == CHIP_FAMILY_ESP32H21 ||
46
+ chipFamily == CHIP_FAMILY_ESP32S31
47
+ ) {
48
+ return null;
49
+ }
50
+
51
+ if (chipFamily == CHIP_FAMILY_ESP32) {
52
+ stubcode = await import("./esp32.json");
53
+ } else if (chipFamily == CHIP_FAMILY_ESP32S2) {
54
+ stubcode = await import("./esp32s2.json");
55
+ } else if (chipFamily == CHIP_FAMILY_ESP32S3) {
56
+ stubcode = await import("./esp32s3.json");
57
+ } else if (chipFamily == CHIP_FAMILY_ESP8266) {
58
+ stubcode = await import("./esp8266.json");
59
+ } else if (chipFamily == CHIP_FAMILY_ESP32C2) {
60
+ stubcode = await import("./esp32c2.json");
61
+ } else if (chipFamily == CHIP_FAMILY_ESP32C3) {
62
+ stubcode = await import("./esp32c3.json");
63
+ } else if (chipFamily == CHIP_FAMILY_ESP32C5) {
64
+ stubcode = await import("./esp32c5.json");
65
+ } else if (chipFamily == CHIP_FAMILY_ESP32C6) {
66
+ stubcode = await import("./esp32c6.json");
67
+ } else if (chipFamily == CHIP_FAMILY_ESP32C61) {
68
+ stubcode = await import("./esp32c61.json");
69
+ } else if (chipFamily == CHIP_FAMILY_ESP32H2) {
70
+ stubcode = await import("./esp32h2.json");
71
+ } else if (chipFamily == CHIP_FAMILY_ESP32P4) {
72
+ // ESP32-P4: Use esp32p4r3.json for Rev. 300+, esp32p4.json for older revisions
73
+ if (chipRevision !== null && chipRevision !== undefined && chipRevision >= 300) {
74
+ stubcode = await import("./esp32p4r3.json");
75
+ } else {
76
+ stubcode = await import("./esp32p4.json");
77
+ }
78
+ }
79
+
80
+ // Base64 decode the text and data
81
+ return {
82
+ ...stubcode,
83
+ text: toByteArray(atob(stubcode.text)),
84
+ data: toByteArray(atob(stubcode.data)),
85
+ };
86
+ };
package/src/util.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @name slipEncode
3
+ * Take an array buffer and return back a new array where
4
+ * 0xdb is replaced with 0xdb 0xdd and 0xc0 is replaced with 0xdb 0xdc
5
+ */
6
+ export const slipEncode = (buffer: number[]): number[] => {
7
+ let encoded = [0xc0];
8
+ for (const byte of buffer) {
9
+ if (byte == 0xdb) {
10
+ encoded = encoded.concat([0xdb, 0xdd]);
11
+ } else if (byte == 0xc0) {
12
+ encoded = encoded.concat([0xdb, 0xdc]);
13
+ } else {
14
+ encoded.push(byte);
15
+ }
16
+ }
17
+ encoded.push(0xc0);
18
+ return encoded;
19
+ };
20
+
21
+ /**
22
+ * @name toByteArray
23
+ * Convert a string to a byte array
24
+ */
25
+ export const toByteArray = (str: string): number[] => {
26
+ const byteArray: number[] = [];
27
+ for (let i = 0; i < str.length; i++) {
28
+ const charcode = str.charCodeAt(i);
29
+ if (charcode <= 0xff) {
30
+ byteArray.push(charcode);
31
+ }
32
+ }
33
+ return byteArray;
34
+ };
35
+
36
+ export const hexFormatter = (bytes: number[]) =>
37
+ "[" + bytes.map((value) => toHex(value)).join(", ") + "]";
38
+
39
+ export const toHex = (value: number, size = 2) => {
40
+ const hex = value.toString(16).toUpperCase();
41
+ if (hex.startsWith("-")) {
42
+ return "-0x" + hex.substring(1).padStart(size, "0");
43
+ } else {
44
+ return "0x" + hex.padStart(size, "0");
45
+ }
46
+ };
47
+
48
+ export const sleep = (ms: number) =>
49
+ new Promise((resolve) => setTimeout(resolve, ms));
Binary file
@@ -0,0 +1,26 @@
1
+ import type { FileSource, BinarySource } from "../shared/types";
2
+ export interface FatFSEntry {
3
+ path: string;
4
+ size: number;
5
+ type: 'file' | 'dir';
6
+ }
7
+ export interface FatFSOptions {
8
+ blockSize?: number;
9
+ blockCount?: number;
10
+ wasmURL?: string | URL;
11
+ formatOnInit?: boolean;
12
+ }
13
+ export interface FatFS {
14
+ format(): void;
15
+ list(): FatFSEntry[];
16
+ readFile(path: string): Uint8Array;
17
+ writeFile(path: string, data: FileSource): void;
18
+ deleteFile(path: string): void;
19
+ toImage(): Uint8Array;
20
+ }
21
+ export declare class FatFSError extends Error {
22
+ readonly code: number;
23
+ constructor(message: string, code: number);
24
+ }
25
+ export declare function createFatFS(options?: FatFSOptions): Promise<FatFS>;
26
+ export declare function createFatFSFromImage(image: BinarySource, options?: FatFSOptions): Promise<FatFS>;
@@ -0,0 +1,343 @@
1
+ const DEFAULT_BLOCK_SIZE = 512;
2
+ const DEFAULT_BLOCK_COUNT = 1024;
3
+ const INITIAL_LIST_BUFFER = 4096;
4
+ const FATFS_ERR_NOT_ENOUGH_CORE = -17;
5
+ export class FatFSError extends Error {
6
+ constructor(message, code) {
7
+ super(message);
8
+ this.code = code;
9
+ this.name = "FatFSError";
10
+ }
11
+ }
12
+ export async function createFatFS(options = {}) {
13
+ console.info("[fatfs-wasm] createFatFS() starting", options);
14
+ const wasmURL = options.wasmURL ?? new URL("./fatfs.wasm", import.meta.url);
15
+ const exports = await instantiateFatFSModule(wasmURL);
16
+ const blockSize = options.blockSize ?? DEFAULT_BLOCK_SIZE;
17
+ const blockCount = options.blockCount ?? DEFAULT_BLOCK_COUNT;
18
+ const initResult = exports.fatfsjs_init(blockSize, blockCount);
19
+ console.info("[fatfs-wasm] fatfsjs_init returned", initResult);
20
+ if (initResult < 0) {
21
+ throw new FatFSError("Failed to initialize FatFS", initResult);
22
+ }
23
+ if (options.formatOnInit) {
24
+ const formatResult = exports.fatfsjs_format();
25
+ console.info("[fatfs-wasm] fatfsjs_format returned", formatResult);
26
+ if (formatResult < 0) {
27
+ throw new FatFSError("Failed to format FatFS volume", formatResult);
28
+ }
29
+ }
30
+ const client = new FatFSClient(exports);
31
+ console.info("[fatfs-wasm] Filesystem initialized");
32
+ return client;
33
+ }
34
+ export async function createFatFSFromImage(image, options = {}) {
35
+ console.info("[fatfs-wasm] createFatFSFromImage() starting");
36
+ const wasmURL = options.wasmURL ?? new URL("./fatfs.wasm", import.meta.url);
37
+ const exports = await instantiateFatFSModule(wasmURL);
38
+ const bytes = asBinaryUint8Array(image);
39
+ const blockSize = options.blockSize ?? DEFAULT_BLOCK_SIZE;
40
+ if (blockSize === 0) {
41
+ throw new Error("blockSize must be a positive integer");
42
+ }
43
+ const inferredBlocks = bytes.length / blockSize;
44
+ const blockCount = options.blockCount ?? inferredBlocks;
45
+ if (blockCount * blockSize !== bytes.length) {
46
+ throw new Error("Image size must equal blockSize * blockCount");
47
+ }
48
+ const ptr = exports.malloc(bytes.length || 1);
49
+ if (!ptr) {
50
+ throw new FatFSError("Failed to allocate WebAssembly memory", FATFS_ERR_NOT_ENOUGH_CORE);
51
+ }
52
+ try {
53
+ // Get heap AFTER malloc to avoid stale reference if memory grows
54
+ const heap = new Uint8Array(exports.memory.buffer);
55
+ heap.set(bytes, ptr);
56
+ const initResult = exports.fatfsjs_init_from_image(blockSize, blockCount, ptr, bytes.length);
57
+ if (initResult < 0) {
58
+ throw new FatFSError("Failed to initialize FatFS from image", initResult);
59
+ }
60
+ }
61
+ finally {
62
+ exports.free(ptr);
63
+ }
64
+ const client = new FatFSClient(exports);
65
+ console.info("[fatfs-wasm] Filesystem initialized from image");
66
+ return client;
67
+ }
68
+ class FatFSClient {
69
+ constructor(exports) {
70
+ this.encoder = new TextEncoder();
71
+ this.decoder = new TextDecoder();
72
+ this.listBufferSize = INITIAL_LIST_BUFFER;
73
+ this.exports = exports;
74
+ this.heapU8 = new Uint8Array(this.exports.memory.buffer);
75
+ }
76
+ format() {
77
+ const result = this.exports.fatfsjs_format();
78
+ this.assertOk(result, "format filesystem");
79
+ }
80
+ list() {
81
+ let capacity = this.listBufferSize;
82
+ while (true) {
83
+ const ptr = this.alloc(capacity);
84
+ try {
85
+ const used = this.exports.fatfsjs_list(ptr, capacity);
86
+ if (used === FATFS_ERR_NOT_ENOUGH_CORE) {
87
+ this.listBufferSize = capacity * 2;
88
+ capacity = this.listBufferSize;
89
+ continue;
90
+ }
91
+ this.assertOk(used, "list files");
92
+ if (used === 0) {
93
+ return [];
94
+ }
95
+ const payload = this.decoder.decode(this.heapU8.subarray(ptr, ptr + used));
96
+ return parseListPayload(payload);
97
+ }
98
+ finally {
99
+ this.exports.free(ptr);
100
+ }
101
+ }
102
+ }
103
+ readFile(path) {
104
+ const normalizedPath = normalizePath(path);
105
+ const pathPtr = this.allocString(normalizedPath);
106
+ try {
107
+ const size = this.exports.fatfsjs_file_size(pathPtr);
108
+ this.assertOk(size, `stat file "${normalizedPath}"`);
109
+ if (size === 0) {
110
+ return new Uint8Array();
111
+ }
112
+ const dataPtr = this.alloc(size);
113
+ try {
114
+ const read = this.exports.fatfsjs_read_file(pathPtr, dataPtr, size);
115
+ this.assertOk(read, `read file "${normalizedPath}"`);
116
+ return this.heapU8.slice(dataPtr, dataPtr + size);
117
+ }
118
+ finally {
119
+ this.exports.free(dataPtr);
120
+ }
121
+ }
122
+ finally {
123
+ this.exports.free(pathPtr);
124
+ }
125
+ }
126
+ writeFile(path, data) {
127
+ const normalizedPath = normalizePath(path);
128
+ const payload = asUint8Array(data, this.encoder);
129
+ const pathPtr = this.allocString(normalizedPath);
130
+ const dataPtr = this.alloc(payload.length);
131
+ try {
132
+ if (payload.length > 0 && dataPtr) {
133
+ this.heapU8.set(payload, dataPtr);
134
+ }
135
+ const result = this.exports.fatfsjs_write_file(pathPtr, payload.length > 0 ? dataPtr : 0, payload.length);
136
+ this.assertOk(result, `write file "${normalizedPath}"`);
137
+ }
138
+ finally {
139
+ if (dataPtr) {
140
+ this.exports.free(dataPtr);
141
+ }
142
+ this.exports.free(pathPtr);
143
+ }
144
+ }
145
+ deleteFile(path) {
146
+ const normalizedPath = normalizePath(path);
147
+ const pathPtr = this.allocString(normalizedPath);
148
+ try {
149
+ const result = this.exports.fatfsjs_delete_file(pathPtr);
150
+ this.assertOk(result, `delete file "${normalizedPath}"`);
151
+ }
152
+ finally {
153
+ this.exports.free(pathPtr);
154
+ }
155
+ }
156
+ toImage() {
157
+ const size = this.exports.fatfsjs_storage_size();
158
+ if (size === 0) {
159
+ return new Uint8Array();
160
+ }
161
+ const ptr = this.alloc(size);
162
+ try {
163
+ const copied = this.exports.fatfsjs_export_image(ptr, size);
164
+ this.assertOk(copied, "export filesystem image");
165
+ return this.heapU8.slice(ptr, ptr + size);
166
+ }
167
+ finally {
168
+ this.exports.free(ptr);
169
+ }
170
+ }
171
+ refreshHeap() {
172
+ if (this.heapU8.buffer !== this.exports.memory.buffer) {
173
+ this.heapU8 = new Uint8Array(this.exports.memory.buffer);
174
+ }
175
+ }
176
+ alloc(size) {
177
+ if (size <= 0) {
178
+ return 0;
179
+ }
180
+ const ptr = this.exports.malloc(size);
181
+ if (!ptr) {
182
+ throw new FatFSError("Failed to allocate WebAssembly memory", FATFS_ERR_NOT_ENOUGH_CORE);
183
+ }
184
+ this.refreshHeap();
185
+ return ptr;
186
+ }
187
+ allocString(value) {
188
+ const encoded = this.encoder.encode(value);
189
+ const ptr = this.alloc(encoded.length + 1);
190
+ this.heapU8.set(encoded, ptr);
191
+ this.heapU8[ptr + encoded.length] = 0;
192
+ return ptr;
193
+ }
194
+ assertOk(code, action) {
195
+ if (code < 0) {
196
+ throw new FatFSError(`Unable to ${action}`, code);
197
+ }
198
+ }
199
+ }
200
+ async function instantiateFatFSModule(input) {
201
+ const source = resolveWasmURL(input);
202
+ console.info("[fatfs-wasm] Fetching wasm from", source.href);
203
+ const wasmContext = { memory: null };
204
+ const imports = createDefaultImports(wasmContext);
205
+ let response = await fetch(source);
206
+ if (!response.ok) {
207
+ throw new Error(`Unable to fetch FatFS wasm from ${response.url}`);
208
+ }
209
+ console.info("[fatfs-wasm] Fetch complete, status", response.status);
210
+ if ("instantiateStreaming" in WebAssembly && typeof WebAssembly.instantiateStreaming === "function") {
211
+ try {
212
+ console.info("[fatfs-wasm] Attempting instantiateStreaming");
213
+ const streaming = await WebAssembly.instantiateStreaming(response, imports);
214
+ wasmContext.memory = getExportedMemory(streaming.instance.exports);
215
+ console.info("[fatfs-wasm] instantiateStreaming succeeded");
216
+ return streaming.instance.exports;
217
+ }
218
+ catch (error) {
219
+ console.warn("Unable to instantiate FatFS wasm via streaming, retrying with arrayBuffer()", error);
220
+ response = await fetch(source);
221
+ if (!response.ok) {
222
+ throw new Error(`Unable to fetch FatFS wasm from ${response.url}`);
223
+ }
224
+ console.info("[fatfs-wasm] Fallback fetch complete, status", response.status);
225
+ }
226
+ }
227
+ console.info("[fatfs-wasm] Instantiating from ArrayBuffer fallback");
228
+ const bytes = await response.arrayBuffer();
229
+ const instance = await WebAssembly.instantiate(bytes, imports);
230
+ wasmContext.memory = getExportedMemory(instance.instance.exports);
231
+ console.info("[fatfs-wasm] instantiate(bytes) succeeded");
232
+ return instance.instance.exports;
233
+ }
234
+ function parseListPayload(payload) {
235
+ if (!payload) {
236
+ return [];
237
+ }
238
+ return payload
239
+ .split("\n")
240
+ .filter((line) => line.length > 0)
241
+ .map((line) => {
242
+ const [rawPath, rawSize] = line.split("\t");
243
+ const path = rawPath ?? "";
244
+ const size = Number(rawSize ?? "0") || 0;
245
+ // Determine type: directories end with '/', files don't
246
+ const type = path.endsWith('/') ? 'dir' : 'file';
247
+ return {
248
+ path: path,
249
+ size: size,
250
+ type: type
251
+ };
252
+ });
253
+ }
254
+ function normalizePath(input) {
255
+ const value = input.trim().replace(/\\/g, "/");
256
+ const withoutRoot = value.replace(/^\/+/, "");
257
+ if (!withoutRoot) {
258
+ throw new Error('Path must point to a file (e.g. "docs/readme.txt")');
259
+ }
260
+ const collapsed = withoutRoot.replace(/\/{2,}/g, "/");
261
+ return collapsed.endsWith("/") ? collapsed.slice(0, -1) : collapsed;
262
+ }
263
+ function asUint8Array(source, encoder) {
264
+ if (typeof source === "string") {
265
+ return encoder.encode(source);
266
+ }
267
+ if (source instanceof Uint8Array) {
268
+ return source;
269
+ }
270
+ if (source instanceof ArrayBuffer) {
271
+ return new Uint8Array(source);
272
+ }
273
+ throw new Error("Unsupported file payload type");
274
+ }
275
+ function asBinaryUint8Array(source) {
276
+ if (source instanceof Uint8Array) {
277
+ return source;
278
+ }
279
+ if (source instanceof ArrayBuffer) {
280
+ return new Uint8Array(source);
281
+ }
282
+ throw new Error("Expected Uint8Array or ArrayBuffer for filesystem image");
283
+ }
284
+ function resolveWasmURL(input) {
285
+ if (input instanceof URL) {
286
+ return input;
287
+ }
288
+ const locationLike = typeof globalThis !== "undefined" && "location" in globalThis
289
+ ? globalThis.location
290
+ : undefined;
291
+ const baseHref = locationLike?.href;
292
+ try {
293
+ return baseHref ? new URL(input, baseHref) : new URL(input);
294
+ }
295
+ catch (error) {
296
+ throw new Error(`Unable to resolve wasm URL from "${input}": ${String(error)}`);
297
+ }
298
+ }
299
+ function createDefaultImports(context) {
300
+ const noop = () => { };
301
+ const ok = () => 0;
302
+ return {
303
+ env: {
304
+ emscripten_notify_memory_growth: noop
305
+ },
306
+ wasi_snapshot_preview1: {
307
+ fd_close: ok,
308
+ fd_seek: ok,
309
+ fd_write: (fd, iov, iovcnt, pnum) => handleFdWrite(context, fd, iov, iovcnt, pnum)
310
+ }
311
+ };
312
+ }
313
+ function handleFdWrite(context, fd, iov, iovcnt, pnum) {
314
+ const memory = context.memory;
315
+ if (!memory) {
316
+ return 0;
317
+ }
318
+ const view = new DataView(memory.buffer);
319
+ let total = 0;
320
+ for (let i = 0; i < iovcnt; i++) {
321
+ const base = iov + i * 8;
322
+ const ptr = view.getUint32(base, true);
323
+ const len = view.getUint32(base + 4, true);
324
+ total += len;
325
+ // Suppress fd_write logging in production (fd 1=stdout, 2=stderr)
326
+ // Uncomment for debugging:
327
+ // if (fd === 1 || fd === 2) {
328
+ // const bytes = new Uint8Array(memory.buffer, ptr, len);
329
+ // const text = new TextDecoder().decode(bytes);
330
+ // console.info(`[fatfs-wasm::fd_write fd=${fd}] ${text}`);
331
+ // }
332
+ }
333
+ view.setUint32(pnum, total, true);
334
+ return 0;
335
+ }
336
+ function getExportedMemory(exports) {
337
+ for (const value of Object.values(exports)) {
338
+ if (value instanceof WebAssembly.Memory) {
339
+ return value;
340
+ }
341
+ }
342
+ return null;
343
+ }
@@ -0,0 +1,156 @@
1
+ import type { Partition } from "../partition";
2
+
3
+ export const LITTLEFS_DEFAULT_BLOCK_SIZE = 4096;
4
+ export const LITTLEFS_BLOCK_SIZE_CANDIDATES = [4096, 2048, 1024, 512];
5
+ export const FATFS_DEFAULT_BLOCK_SIZE = 4096;
6
+ export const FATFS_BLOCK_SIZE_CANDIDATES = [4096, 2048, 1024, 512];
7
+
8
+ /**
9
+ * Filesystem types based on partition subtype
10
+ */
11
+ export enum FilesystemType {
12
+ UNKNOWN = "unknown",
13
+ LITTLEFS = "littlefs",
14
+ FATFS = "fatfs",
15
+ SPIFFS = "spiffs",
16
+ }
17
+
18
+ /**
19
+ * Detect filesystem type from partition information
20
+ * Note: This only provides a hint. LittleFS is often stored in SPIFFS partitions (0x82).
21
+ * Use detectFilesystemFromImage() for accurate detection.
22
+ */
23
+ export function detectFilesystemType(partition: Partition): FilesystemType {
24
+ if (partition.type !== 0x01) {
25
+ return FilesystemType.UNKNOWN;
26
+ }
27
+
28
+ switch (partition.subtype) {
29
+ case 0x81:
30
+ return FilesystemType.FATFS;
31
+ case 0x82:
32
+ return FilesystemType.UNKNOWN;
33
+ default:
34
+ return FilesystemType.UNKNOWN;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Detect filesystem type from image data
40
+ * Reads the first 8KB to identify filesystem by magic headers and ASCII signatures
41
+ */
42
+ export function detectFilesystemFromImage(
43
+ imageData: Uint8Array,
44
+ ): FilesystemType {
45
+ if (imageData.length < 512) {
46
+ return FilesystemType.UNKNOWN;
47
+ }
48
+
49
+ const searchSize = Math.min(8192, imageData.length);
50
+
51
+ // Check for LittleFS magic numbers and ASCII signature
52
+ for (let offset = 0; offset < searchSize - 8; offset++) {
53
+ // Check for LittleFS magic: "littlefs" or "lfs\x00" or version-specific magic
54
+ const magic32 =
55
+ imageData[offset] |
56
+ (imageData[offset + 1] << 8) |
57
+ (imageData[offset + 2] << 16) |
58
+ (imageData[offset + 3] << 24);
59
+
60
+ // LittleFS v2.x magic: 0x32736c66 ("lfs2" in little-endian)
61
+ // LittleFS v1.x magic: 0x31736c66 ("lfs1" in little-endian)
62
+ if (magic32 === 0x32736c66 || magic32 === 0x31736c66) {
63
+ return FilesystemType.LITTLEFS;
64
+ }
65
+
66
+ // Check for ASCII "littlefs" string
67
+ if (offset + 8 <= searchSize) {
68
+ const str = String.fromCharCode(
69
+ imageData[offset],
70
+ imageData[offset + 1],
71
+ imageData[offset + 2],
72
+ imageData[offset + 3],
73
+ imageData[offset + 4],
74
+ imageData[offset + 5],
75
+ imageData[offset + 6],
76
+ imageData[offset + 7],
77
+ );
78
+ if (str === "littlefs") {
79
+ return FilesystemType.LITTLEFS;
80
+ }
81
+ }
82
+ }
83
+
84
+ // Check for FAT filesystem signatures
85
+ if (imageData.length >= 512) {
86
+ const bootSig = imageData[510] | (imageData[511] << 8);
87
+ if (bootSig === 0xaa55) {
88
+ const fat16Sig =
89
+ imageData.length >= 62
90
+ ? String.fromCharCode(
91
+ imageData[54],
92
+ imageData[55],
93
+ imageData[56],
94
+ imageData[57],
95
+ imageData[58],
96
+ )
97
+ : "";
98
+ const fat32Sig =
99
+ imageData.length >= 90
100
+ ? String.fromCharCode(
101
+ imageData[82],
102
+ imageData[83],
103
+ imageData[84],
104
+ imageData[85],
105
+ imageData[86],
106
+ )
107
+ : "";
108
+
109
+ if (fat16Sig.startsWith("FAT") || fat32Sig.startsWith("FAT")) {
110
+ return FilesystemType.FATFS;
111
+ }
112
+ }
113
+ }
114
+
115
+ // Check for SPIFFS magic (0x20140529)
116
+ if (imageData.length >= 4) {
117
+ const spiffsMagic =
118
+ imageData[0] |
119
+ (imageData[1] << 8) |
120
+ (imageData[2] << 16) |
121
+ (imageData[3] << 24);
122
+ if (spiffsMagic === 0x20140529) {
123
+ return FilesystemType.SPIFFS;
124
+ }
125
+ }
126
+
127
+ return FilesystemType.UNKNOWN;
128
+ }
129
+
130
+ /**
131
+ * Get appropriate block size for filesystem type
132
+ */
133
+ export function getDefaultBlockSize(fsType: FilesystemType): number {
134
+ switch (fsType) {
135
+ case FilesystemType.FATFS:
136
+ return FATFS_DEFAULT_BLOCK_SIZE;
137
+ case FilesystemType.LITTLEFS:
138
+ return LITTLEFS_DEFAULT_BLOCK_SIZE;
139
+ default:
140
+ return 4096;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Get block size candidates for filesystem type
146
+ */
147
+ export function getBlockSizeCandidates(fsType: FilesystemType): number[] {
148
+ switch (fsType) {
149
+ case FilesystemType.FATFS:
150
+ return FATFS_BLOCK_SIZE_CANDIDATES;
151
+ case FilesystemType.LITTLEFS:
152
+ return LITTLEFS_BLOCK_SIZE_CANDIDATES;
153
+ default:
154
+ return [4096, 2048, 1024, 512];
155
+ }
156
+ }