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.
- package/README.md +31 -0
- package/css/dark.css +156 -0
- package/css/light.css +156 -0
- package/css/style.css +870 -0
- package/dist/const.d.ts +277 -0
- package/dist/const.js +511 -0
- package/dist/esp_loader.d.ts +222 -0
- package/dist/esp_loader.js +1466 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +15 -0
- package/dist/lib/spiffs/index.d.ts +15 -0
- package/dist/lib/spiffs/index.js +16 -0
- package/dist/lib/spiffs/spiffs.d.ts +26 -0
- package/dist/lib/spiffs/spiffs.js +132 -0
- package/dist/lib/spiffs/spiffsBlock.d.ts +36 -0
- package/dist/lib/spiffs/spiffsBlock.js +140 -0
- package/dist/lib/spiffs/spiffsConfig.d.ts +63 -0
- package/dist/lib/spiffs/spiffsConfig.js +79 -0
- package/dist/lib/spiffs/spiffsPage.d.ts +45 -0
- package/dist/lib/spiffs/spiffsPage.js +260 -0
- package/dist/lib/spiffs/spiffsReader.d.ts +19 -0
- package/dist/lib/spiffs/spiffsReader.js +192 -0
- package/dist/partition.d.ts +26 -0
- package/dist/partition.js +129 -0
- package/dist/struct.d.ts +2 -0
- package/dist/struct.js +91 -0
- package/dist/stubs/esp32.json +8 -0
- package/dist/stubs/esp32c2.json +8 -0
- package/dist/stubs/esp32c3.json +8 -0
- package/dist/stubs/esp32c5.json +8 -0
- package/dist/stubs/esp32c6.json +8 -0
- package/dist/stubs/esp32c61.json +8 -0
- package/dist/stubs/esp32h2.json +8 -0
- package/dist/stubs/esp32p4.json +8 -0
- package/dist/stubs/esp32p4r3.json +8 -0
- package/dist/stubs/esp32s2.json +8 -0
- package/dist/stubs/esp32s3.json +8 -0
- package/dist/stubs/esp8266.json +8 -0
- package/dist/stubs/index.d.ts +10 -0
- package/dist/stubs/index.js +56 -0
- package/dist/util.d.ts +14 -0
- package/dist/util.js +46 -0
- package/dist/wasm/filesystems.d.ts +33 -0
- package/dist/wasm/filesystems.js +114 -0
- package/dist/web/esp32-D955RjN9.js +16 -0
- package/dist/web/esp32c2-CJkxHDQi.js +16 -0
- package/dist/web/esp32c3-BhUHzH0o.js +16 -0
- package/dist/web/esp32c5-Chs0HtmA.js +16 -0
- package/dist/web/esp32c6-D6mPN6ut.js +16 -0
- package/dist/web/esp32c61-CQiYCWAs.js +16 -0
- package/dist/web/esp32h2-LsKJE9AS.js +16 -0
- package/dist/web/esp32p4-7nWC-HiD.js +16 -0
- package/dist/web/esp32p4r3-CwiPecZW.js +16 -0
- package/dist/web/esp32s2-CtqVheSJ.js +16 -0
- package/dist/web/esp32s3-CRbtB0QR.js +16 -0
- package/dist/web/esp8266-nEkNAo8K.js +16 -0
- package/dist/web/index.js +7265 -0
- package/electron/main.js +333 -0
- package/electron/preload.js +37 -0
- package/eslint.config.js +22 -0
- package/index.html +408 -0
- package/js/modules/esp32-D955RjN9.js +16 -0
- package/js/modules/esp32c2-CJkxHDQi.js +16 -0
- package/js/modules/esp32c3-BhUHzH0o.js +16 -0
- package/js/modules/esp32c5-Chs0HtmA.js +16 -0
- package/js/modules/esp32c6-D6mPN6ut.js +16 -0
- package/js/modules/esp32c61-CQiYCWAs.js +16 -0
- package/js/modules/esp32h2-LsKJE9AS.js +16 -0
- package/js/modules/esp32p4-7nWC-HiD.js +16 -0
- package/js/modules/esp32p4r3-CwiPecZW.js +16 -0
- package/js/modules/esp32s2-CtqVheSJ.js +16 -0
- package/js/modules/esp32s3-CRbtB0QR.js +16 -0
- package/js/modules/esp8266-nEkNAo8K.js +16 -0
- package/js/modules/esptool.js +7265 -0
- package/js/script.js +2237 -0
- package/js/utilities.js +182 -0
- package/license.md +11 -0
- package/package.json +61 -0
- package/script/build +12 -0
- package/script/develop +17 -0
- package/src/const.ts +599 -0
- package/src/esp_loader.ts +1907 -0
- package/src/index.ts +63 -0
- package/src/lib/spiffs/index.ts +22 -0
- package/src/lib/spiffs/spiffs.ts +175 -0
- package/src/lib/spiffs/spiffsBlock.ts +204 -0
- package/src/lib/spiffs/spiffsConfig.ts +140 -0
- package/src/lib/spiffs/spiffsPage.ts +357 -0
- package/src/lib/spiffs/spiffsReader.ts +280 -0
- package/src/partition.ts +155 -0
- package/src/struct.ts +108 -0
- package/src/stubs/README.md +3 -0
- package/src/stubs/esp32.json +8 -0
- package/src/stubs/esp32c2.json +8 -0
- package/src/stubs/esp32c3.json +8 -0
- package/src/stubs/esp32c5.json +8 -0
- package/src/stubs/esp32c6.json +8 -0
- package/src/stubs/esp32c61.json +8 -0
- package/src/stubs/esp32h2.json +8 -0
- package/src/stubs/esp32p4.json +8 -0
- package/src/stubs/esp32p4r3.json +8 -0
- package/src/stubs/esp32s2.json +8 -0
- package/src/stubs/esp32s3.json +8 -0
- package/src/stubs/esp8266.json +8 -0
- package/src/stubs/index.ts +86 -0
- package/src/util.ts +49 -0
- package/src/wasm/fatfs/fatfs.wasm +0 -0
- package/src/wasm/fatfs/index.d.ts +26 -0
- package/src/wasm/fatfs/index.js +343 -0
- package/src/wasm/filesystems.ts +156 -0
- package/src/wasm/littlefs/index.d.ts +83 -0
- package/src/wasm/littlefs/index.js +529 -0
- package/src/wasm/littlefs/littlefs.js +2 -0
- package/src/wasm/littlefs/littlefs.wasm +0 -0
- package/src/wasm/shared/types.ts +13 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPIFFS Page Classes
|
|
3
|
+
* Based on ESP-IDF spiffsgen.py
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
SpiffsBuildConfig,
|
|
8
|
+
SpiffsFullError,
|
|
9
|
+
SPIFFS_PH_FLAG_USED_FINAL_INDEX,
|
|
10
|
+
SPIFFS_PH_FLAG_USED_FINAL,
|
|
11
|
+
SPIFFS_TYPE_FILE,
|
|
12
|
+
} from "./spiffsConfig";
|
|
13
|
+
|
|
14
|
+
export abstract class SpiffsPage {
|
|
15
|
+
protected buildConfig: SpiffsBuildConfig;
|
|
16
|
+
protected bix: number;
|
|
17
|
+
|
|
18
|
+
constructor(bix: number, buildConfig: SpiffsBuildConfig) {
|
|
19
|
+
this.buildConfig = buildConfig;
|
|
20
|
+
this.bix = bix;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
abstract toBinary(): Uint8Array;
|
|
24
|
+
|
|
25
|
+
protected pack(format: string, ...values: number[]): Uint8Array {
|
|
26
|
+
const buffer = new ArrayBuffer(this.calcSize(format));
|
|
27
|
+
const view = new DataView(buffer);
|
|
28
|
+
let offset = 0;
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < format.length; i++) {
|
|
31
|
+
const type = format[i];
|
|
32
|
+
const value = values[i];
|
|
33
|
+
|
|
34
|
+
switch (type) {
|
|
35
|
+
case "B": // unsigned char (1 byte)
|
|
36
|
+
view.setUint8(offset, value);
|
|
37
|
+
offset += 1;
|
|
38
|
+
break;
|
|
39
|
+
case "H": // unsigned short (2 bytes)
|
|
40
|
+
if (this.buildConfig.endianness === "little") {
|
|
41
|
+
view.setUint16(offset, value, true);
|
|
42
|
+
} else {
|
|
43
|
+
view.setUint16(offset, value, false);
|
|
44
|
+
}
|
|
45
|
+
offset += 2;
|
|
46
|
+
break;
|
|
47
|
+
case "I": // unsigned int (4 bytes)
|
|
48
|
+
if (this.buildConfig.endianness === "little") {
|
|
49
|
+
view.setUint32(offset, value, true);
|
|
50
|
+
} else {
|
|
51
|
+
view.setUint32(offset, value, false);
|
|
52
|
+
}
|
|
53
|
+
offset += 4;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return new Uint8Array(buffer);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected unpack(format: string, data: Uint8Array, offset = 0): number[] {
|
|
62
|
+
const view = new DataView(data.buffer, data.byteOffset + offset);
|
|
63
|
+
const results: number[] = [];
|
|
64
|
+
let pos = 0;
|
|
65
|
+
|
|
66
|
+
for (const type of format) {
|
|
67
|
+
switch (type) {
|
|
68
|
+
case "B":
|
|
69
|
+
results.push(view.getUint8(pos));
|
|
70
|
+
pos += 1;
|
|
71
|
+
break;
|
|
72
|
+
case "H":
|
|
73
|
+
results.push(
|
|
74
|
+
this.buildConfig.endianness === "little"
|
|
75
|
+
? view.getUint16(pos, true)
|
|
76
|
+
: view.getUint16(pos, false),
|
|
77
|
+
);
|
|
78
|
+
pos += 2;
|
|
79
|
+
break;
|
|
80
|
+
case "I":
|
|
81
|
+
results.push(
|
|
82
|
+
this.buildConfig.endianness === "little"
|
|
83
|
+
? view.getUint32(pos, true)
|
|
84
|
+
: view.getUint32(pos, false),
|
|
85
|
+
);
|
|
86
|
+
pos += 4;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private calcSize(format: string): number {
|
|
95
|
+
let size = 0;
|
|
96
|
+
for (const type of format) {
|
|
97
|
+
switch (type) {
|
|
98
|
+
case "B":
|
|
99
|
+
size += 1;
|
|
100
|
+
break;
|
|
101
|
+
case "H":
|
|
102
|
+
size += 2;
|
|
103
|
+
break;
|
|
104
|
+
case "I":
|
|
105
|
+
size += 4;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return size;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export abstract class SpiffsObjPageWithIdx extends SpiffsPage {
|
|
114
|
+
protected objId: number;
|
|
115
|
+
|
|
116
|
+
constructor(objId: number, buildConfig: SpiffsBuildConfig) {
|
|
117
|
+
super(0, buildConfig);
|
|
118
|
+
this.objId = objId;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
getObjId(): number {
|
|
122
|
+
return this.objId;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class SpiffsObjLuPage extends SpiffsPage {
|
|
127
|
+
private objIdsLimit: number;
|
|
128
|
+
private objIds: Array<[number, string]>; // [objId, pageType]
|
|
129
|
+
|
|
130
|
+
constructor(bix: number, buildConfig: SpiffsBuildConfig) {
|
|
131
|
+
super(bix, buildConfig);
|
|
132
|
+
this.objIdsLimit = this.buildConfig.OBJ_LU_PAGES_OBJ_IDS_LIM;
|
|
133
|
+
this.objIds = [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private calcMagic(blocksLim: number): number {
|
|
137
|
+
let magic = 0x20140529 ^ this.buildConfig.pageSize;
|
|
138
|
+
if (this.buildConfig.useMagicLen) {
|
|
139
|
+
magic = magic ^ (blocksLim - this.bix);
|
|
140
|
+
}
|
|
141
|
+
const mask = (1 << (8 * this.buildConfig.objIdLen)) - 1;
|
|
142
|
+
return magic & mask;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
registerPage(page: SpiffsObjPageWithIdx): void {
|
|
146
|
+
if (this.objIdsLimit <= 0) {
|
|
147
|
+
throw new SpiffsFullError();
|
|
148
|
+
}
|
|
149
|
+
const pageType = page instanceof SpiffsObjIndexPage ? "index" : "data";
|
|
150
|
+
this.objIds.push([page.getObjId(), pageType]);
|
|
151
|
+
this.objIdsLimit--;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
toBinary(): Uint8Array {
|
|
155
|
+
const img = new Uint8Array(this.buildConfig.pageSize);
|
|
156
|
+
img.fill(0xff);
|
|
157
|
+
|
|
158
|
+
let offset = 0;
|
|
159
|
+
for (const [objId, pageType] of this.objIds) {
|
|
160
|
+
let id = objId;
|
|
161
|
+
if (pageType === "index") {
|
|
162
|
+
id ^= 1 << (this.buildConfig.objIdLen * 8 - 1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const packed = this.pack(
|
|
166
|
+
this.buildConfig.objIdLen === 1
|
|
167
|
+
? "B"
|
|
168
|
+
: this.buildConfig.objIdLen === 2
|
|
169
|
+
? "H"
|
|
170
|
+
: "I",
|
|
171
|
+
id,
|
|
172
|
+
);
|
|
173
|
+
img.set(packed, offset);
|
|
174
|
+
offset += packed.length;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return img;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
magicfy(blocksLim: number): void {
|
|
181
|
+
const remaining = this.objIdsLimit;
|
|
182
|
+
const emptyObjId = (1 << (this.buildConfig.objIdLen * 8)) - 1;
|
|
183
|
+
|
|
184
|
+
if (remaining >= 2) {
|
|
185
|
+
for (let i = 0; i < remaining; i++) {
|
|
186
|
+
if (i === remaining - 2) {
|
|
187
|
+
this.objIds.push([this.calcMagic(blocksLim), "data"]);
|
|
188
|
+
break;
|
|
189
|
+
} else {
|
|
190
|
+
this.objIds.push([emptyObjId, "data"]);
|
|
191
|
+
}
|
|
192
|
+
this.objIdsLimit--;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export class SpiffsObjIndexPage extends SpiffsObjPageWithIdx {
|
|
199
|
+
private spanIx: number;
|
|
200
|
+
private name: string;
|
|
201
|
+
private size: number;
|
|
202
|
+
private pagesLim: number;
|
|
203
|
+
private pages: number[];
|
|
204
|
+
|
|
205
|
+
constructor(
|
|
206
|
+
objId: number,
|
|
207
|
+
spanIx: number,
|
|
208
|
+
size: number,
|
|
209
|
+
name: string,
|
|
210
|
+
buildConfig: SpiffsBuildConfig,
|
|
211
|
+
) {
|
|
212
|
+
super(objId, buildConfig);
|
|
213
|
+
this.spanIx = spanIx;
|
|
214
|
+
this.name = name;
|
|
215
|
+
this.size = size;
|
|
216
|
+
|
|
217
|
+
if (this.spanIx === 0) {
|
|
218
|
+
this.pagesLim = this.buildConfig.OBJ_INDEX_PAGES_OBJ_IDS_HEAD_LIM;
|
|
219
|
+
} else {
|
|
220
|
+
this.pagesLim = this.buildConfig.OBJ_INDEX_PAGES_OBJ_IDS_LIM;
|
|
221
|
+
}
|
|
222
|
+
this.pages = [];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
registerPage(page: SpiffsObjDataPage): void {
|
|
226
|
+
if (this.pagesLim <= 0) {
|
|
227
|
+
throw new SpiffsFullError();
|
|
228
|
+
}
|
|
229
|
+
this.pages.push(page.offset);
|
|
230
|
+
this.pagesLim--;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
toBinary(): Uint8Array {
|
|
234
|
+
const img = new Uint8Array(this.buildConfig.pageSize);
|
|
235
|
+
img.fill(0xff);
|
|
236
|
+
|
|
237
|
+
const objId = this.objId ^ (1 << (this.buildConfig.objIdLen * 8 - 1));
|
|
238
|
+
|
|
239
|
+
const format =
|
|
240
|
+
(this.buildConfig.objIdLen === 1
|
|
241
|
+
? "B"
|
|
242
|
+
: this.buildConfig.objIdLen === 2
|
|
243
|
+
? "H"
|
|
244
|
+
: "I") +
|
|
245
|
+
(this.buildConfig.spanIxLen === 1
|
|
246
|
+
? "B"
|
|
247
|
+
: this.buildConfig.spanIxLen === 2
|
|
248
|
+
? "H"
|
|
249
|
+
: "I") +
|
|
250
|
+
"B";
|
|
251
|
+
|
|
252
|
+
let offset = 0;
|
|
253
|
+
const header = this.pack(
|
|
254
|
+
format,
|
|
255
|
+
objId,
|
|
256
|
+
this.spanIx,
|
|
257
|
+
SPIFFS_PH_FLAG_USED_FINAL_INDEX,
|
|
258
|
+
);
|
|
259
|
+
img.set(header, offset);
|
|
260
|
+
offset += header.length;
|
|
261
|
+
|
|
262
|
+
// Add padding
|
|
263
|
+
offset += this.buildConfig.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED_PAD;
|
|
264
|
+
|
|
265
|
+
// If first index page, add filename, type and size
|
|
266
|
+
if (this.spanIx === 0) {
|
|
267
|
+
const sizeType = this.pack("IB", this.size, SPIFFS_TYPE_FILE);
|
|
268
|
+
img.set(sizeType, offset);
|
|
269
|
+
offset += sizeType.length;
|
|
270
|
+
|
|
271
|
+
// Write filename with proper null-termination
|
|
272
|
+
const nameBytes = new TextEncoder().encode(this.name);
|
|
273
|
+
// Ensure we don't exceed objNameLen
|
|
274
|
+
const bytesToWrite = Math.min(
|
|
275
|
+
nameBytes.length,
|
|
276
|
+
this.buildConfig.objNameLen,
|
|
277
|
+
);
|
|
278
|
+
img.set(nameBytes.slice(0, bytesToWrite), offset);
|
|
279
|
+
// The rest is already 0xFF from img.fill(0xff), but SPIFFS expects 0x00 for unused name bytes
|
|
280
|
+
// Fill remaining name bytes with 0x00
|
|
281
|
+
for (let i = bytesToWrite; i < this.buildConfig.objNameLen; i++) {
|
|
282
|
+
img[offset + i] = 0x00;
|
|
283
|
+
}
|
|
284
|
+
offset +=
|
|
285
|
+
this.buildConfig.objNameLen +
|
|
286
|
+
this.buildConfig.metaLen +
|
|
287
|
+
this.buildConfig.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED_PAD;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Add page indices
|
|
291
|
+
for (const page of this.pages) {
|
|
292
|
+
// Calculate page index by dividing page offset by page size
|
|
293
|
+
// pageSize is always a power of 2, so integer division is safe
|
|
294
|
+
const pageIx = Math.floor(page / this.buildConfig.pageSize);
|
|
295
|
+
const pageIxPacked = this.pack(
|
|
296
|
+
this.buildConfig.pageIxLen === 1
|
|
297
|
+
? "B"
|
|
298
|
+
: this.buildConfig.pageIxLen === 2
|
|
299
|
+
? "H"
|
|
300
|
+
: "I",
|
|
301
|
+
pageIx,
|
|
302
|
+
);
|
|
303
|
+
img.set(pageIxPacked, offset);
|
|
304
|
+
offset += pageIxPacked.length;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return img;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export class SpiffsObjDataPage extends SpiffsObjPageWithIdx {
|
|
312
|
+
offset: number;
|
|
313
|
+
private spanIx: number;
|
|
314
|
+
private contents: Uint8Array;
|
|
315
|
+
|
|
316
|
+
constructor(
|
|
317
|
+
offset: number,
|
|
318
|
+
objId: number,
|
|
319
|
+
spanIx: number,
|
|
320
|
+
contents: Uint8Array,
|
|
321
|
+
buildConfig: SpiffsBuildConfig,
|
|
322
|
+
) {
|
|
323
|
+
super(objId, buildConfig);
|
|
324
|
+
this.offset = offset;
|
|
325
|
+
this.spanIx = spanIx;
|
|
326
|
+
this.contents = contents;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
toBinary(): Uint8Array {
|
|
330
|
+
const img = new Uint8Array(this.buildConfig.pageSize);
|
|
331
|
+
img.fill(0xff);
|
|
332
|
+
|
|
333
|
+
const format =
|
|
334
|
+
(this.buildConfig.objIdLen === 1
|
|
335
|
+
? "B"
|
|
336
|
+
: this.buildConfig.objIdLen === 2
|
|
337
|
+
? "H"
|
|
338
|
+
: "I") +
|
|
339
|
+
(this.buildConfig.spanIxLen === 1
|
|
340
|
+
? "B"
|
|
341
|
+
: this.buildConfig.spanIxLen === 2
|
|
342
|
+
? "H"
|
|
343
|
+
: "I") +
|
|
344
|
+
"B";
|
|
345
|
+
|
|
346
|
+
const header = this.pack(
|
|
347
|
+
format,
|
|
348
|
+
this.objId,
|
|
349
|
+
this.spanIx,
|
|
350
|
+
SPIFFS_PH_FLAG_USED_FINAL,
|
|
351
|
+
);
|
|
352
|
+
img.set(header, 0);
|
|
353
|
+
img.set(this.contents, header.length);
|
|
354
|
+
|
|
355
|
+
return img;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPIFFS Reader - Parse and extract files from SPIFFS images
|
|
3
|
+
* Based on ESP-IDF spiffsgen.py extract_files() method
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
SpiffsBuildConfig,
|
|
8
|
+
SPIFFS_PH_FLAG_USED_FINAL_INDEX,
|
|
9
|
+
SPIFFS_PH_FLAG_USED_FINAL,
|
|
10
|
+
SPIFFS_PH_FLAG_LEN,
|
|
11
|
+
SPIFFS_PH_IX_SIZE_LEN,
|
|
12
|
+
SPIFFS_PH_IX_OBJ_TYPE_LEN,
|
|
13
|
+
} from "./spiffsConfig";
|
|
14
|
+
import type { SpiffsFile } from "./spiffs";
|
|
15
|
+
|
|
16
|
+
interface FileInfo {
|
|
17
|
+
name: string | null;
|
|
18
|
+
size: number;
|
|
19
|
+
dataPages: Array<[number, Uint8Array]>; // [span_ix, content]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class SpiffsReader {
|
|
23
|
+
private buildConfig: SpiffsBuildConfig;
|
|
24
|
+
private imageData: Uint8Array;
|
|
25
|
+
private filesMap: Map<number, FileInfo>;
|
|
26
|
+
|
|
27
|
+
constructor(imageData: Uint8Array, buildConfig: SpiffsBuildConfig) {
|
|
28
|
+
this.imageData = imageData;
|
|
29
|
+
this.buildConfig = buildConfig;
|
|
30
|
+
this.filesMap = new Map();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private unpack(format: string, data: Uint8Array, offset = 0): number[] {
|
|
34
|
+
const view = new DataView(data.buffer, data.byteOffset + offset);
|
|
35
|
+
const results: number[] = [];
|
|
36
|
+
let pos = 0;
|
|
37
|
+
|
|
38
|
+
for (const type of format) {
|
|
39
|
+
switch (type) {
|
|
40
|
+
case "B":
|
|
41
|
+
results.push(view.getUint8(pos));
|
|
42
|
+
pos += 1;
|
|
43
|
+
break;
|
|
44
|
+
case "H":
|
|
45
|
+
results.push(
|
|
46
|
+
this.buildConfig.endianness === "little"
|
|
47
|
+
? view.getUint16(pos, true)
|
|
48
|
+
: view.getUint16(pos, false),
|
|
49
|
+
);
|
|
50
|
+
pos += 2;
|
|
51
|
+
break;
|
|
52
|
+
case "I":
|
|
53
|
+
results.push(
|
|
54
|
+
this.buildConfig.endianness === "little"
|
|
55
|
+
? view.getUint32(pos, true)
|
|
56
|
+
: view.getUint32(pos, false),
|
|
57
|
+
);
|
|
58
|
+
pos += 4;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return results;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
parse(): void {
|
|
67
|
+
const blocksCount = Math.floor(
|
|
68
|
+
this.imageData.length / this.buildConfig.blockSize,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
for (let bix = 0; bix < blocksCount; bix++) {
|
|
72
|
+
const blockOffset = bix * this.buildConfig.blockSize;
|
|
73
|
+
const blockData = this.imageData.slice(
|
|
74
|
+
blockOffset,
|
|
75
|
+
blockOffset + this.buildConfig.blockSize,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
this.parseBlock(blockData);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private parseBlock(blockData: Uint8Array): void {
|
|
83
|
+
// Parse lookup pages to find valid objects
|
|
84
|
+
for (
|
|
85
|
+
let pageIdx = 0;
|
|
86
|
+
pageIdx < this.buildConfig.OBJ_LU_PAGES_PER_BLOCK;
|
|
87
|
+
pageIdx++
|
|
88
|
+
) {
|
|
89
|
+
const luPageOffset = pageIdx * this.buildConfig.pageSize;
|
|
90
|
+
const luPageData = blockData.slice(
|
|
91
|
+
luPageOffset,
|
|
92
|
+
luPageOffset + this.buildConfig.pageSize,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Parse object IDs from lookup page
|
|
96
|
+
for (let i = 0; i < luPageData.length; i += this.buildConfig.objIdLen) {
|
|
97
|
+
if (i + this.buildConfig.objIdLen > luPageData.length) break;
|
|
98
|
+
|
|
99
|
+
const objIdBytes = luPageData.slice(i, i + this.buildConfig.objIdLen);
|
|
100
|
+
const [objId] = this.unpack(
|
|
101
|
+
this.buildConfig.objIdLen === 1
|
|
102
|
+
? "B"
|
|
103
|
+
: this.buildConfig.objIdLen === 2
|
|
104
|
+
? "H"
|
|
105
|
+
: "I",
|
|
106
|
+
objIdBytes,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Check if it's a valid object (not erased/empty)
|
|
110
|
+
const emptyValue = (1 << (this.buildConfig.objIdLen * 8)) - 1;
|
|
111
|
+
if (objId === emptyValue) continue;
|
|
112
|
+
|
|
113
|
+
// Check if it's an index page (MSB set)
|
|
114
|
+
const isIndex =
|
|
115
|
+
(objId & (1 << (this.buildConfig.objIdLen * 8 - 1))) !== 0;
|
|
116
|
+
const realObjId = objId & ~(1 << (this.buildConfig.objIdLen * 8 - 1));
|
|
117
|
+
|
|
118
|
+
if (isIndex && !this.filesMap.has(realObjId)) {
|
|
119
|
+
this.filesMap.set(realObjId, {
|
|
120
|
+
name: null,
|
|
121
|
+
size: 0,
|
|
122
|
+
dataPages: [],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Parse actual pages to get file metadata and content
|
|
129
|
+
for (
|
|
130
|
+
let pageIdx = this.buildConfig.OBJ_LU_PAGES_PER_BLOCK;
|
|
131
|
+
pageIdx < this.buildConfig.PAGES_PER_BLOCK;
|
|
132
|
+
pageIdx++
|
|
133
|
+
) {
|
|
134
|
+
const pageOffset = pageIdx * this.buildConfig.pageSize;
|
|
135
|
+
const pageData = blockData.slice(
|
|
136
|
+
pageOffset,
|
|
137
|
+
pageOffset + this.buildConfig.pageSize,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
this.parsePage(pageData);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private parsePage(pageData: Uint8Array): void {
|
|
145
|
+
// Parse page header
|
|
146
|
+
const headerFormat =
|
|
147
|
+
(this.buildConfig.objIdLen === 1
|
|
148
|
+
? "B"
|
|
149
|
+
: this.buildConfig.objIdLen === 2
|
|
150
|
+
? "H"
|
|
151
|
+
: "I") +
|
|
152
|
+
(this.buildConfig.spanIxLen === 1
|
|
153
|
+
? "B"
|
|
154
|
+
: this.buildConfig.spanIxLen === 2
|
|
155
|
+
? "H"
|
|
156
|
+
: "I") +
|
|
157
|
+
"B";
|
|
158
|
+
|
|
159
|
+
const headerSize =
|
|
160
|
+
this.buildConfig.objIdLen +
|
|
161
|
+
this.buildConfig.spanIxLen +
|
|
162
|
+
SPIFFS_PH_FLAG_LEN;
|
|
163
|
+
|
|
164
|
+
if (pageData.length < headerSize) return;
|
|
165
|
+
|
|
166
|
+
const [objId, spanIx, flags] = this.unpack(headerFormat, pageData);
|
|
167
|
+
|
|
168
|
+
// Check for valid page
|
|
169
|
+
const emptyId = (1 << (this.buildConfig.objIdLen * 8)) - 1;
|
|
170
|
+
if (objId === emptyId) return;
|
|
171
|
+
|
|
172
|
+
const isIndex = (objId & (1 << (this.buildConfig.objIdLen * 8 - 1))) !== 0;
|
|
173
|
+
const realObjId = objId & ~(1 << (this.buildConfig.objIdLen * 8 - 1));
|
|
174
|
+
|
|
175
|
+
if (isIndex && flags === SPIFFS_PH_FLAG_USED_FINAL_INDEX) {
|
|
176
|
+
// Index page - contains file metadata
|
|
177
|
+
if (!this.filesMap.has(realObjId)) {
|
|
178
|
+
this.filesMap.set(realObjId, {
|
|
179
|
+
name: null,
|
|
180
|
+
size: 0,
|
|
181
|
+
dataPages: [],
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Only first index page (span_ix == 0) has filename and size
|
|
186
|
+
if (spanIx === 0) {
|
|
187
|
+
this.parseIndexPage(pageData, headerSize, realObjId);
|
|
188
|
+
}
|
|
189
|
+
} else if (!isIndex && flags === SPIFFS_PH_FLAG_USED_FINAL) {
|
|
190
|
+
// Data page - contains file content
|
|
191
|
+
if (this.filesMap.has(realObjId)) {
|
|
192
|
+
const contentStart = headerSize;
|
|
193
|
+
const content = pageData.slice(
|
|
194
|
+
contentStart,
|
|
195
|
+
contentStart + this.buildConfig.OBJ_DATA_PAGE_CONTENT_LEN,
|
|
196
|
+
);
|
|
197
|
+
this.filesMap.get(realObjId)!.dataPages.push([spanIx, content]);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private parseIndexPage(
|
|
203
|
+
pageData: Uint8Array,
|
|
204
|
+
headerSize: number,
|
|
205
|
+
objId: number,
|
|
206
|
+
): void {
|
|
207
|
+
// Skip to size and type fields
|
|
208
|
+
let offset =
|
|
209
|
+
headerSize + this.buildConfig.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED_PAD;
|
|
210
|
+
|
|
211
|
+
const sizeTypeFormat = "IB";
|
|
212
|
+
const sizeTypeSize = SPIFFS_PH_IX_SIZE_LEN + SPIFFS_PH_IX_OBJ_TYPE_LEN;
|
|
213
|
+
|
|
214
|
+
if (offset + sizeTypeSize <= pageData.length) {
|
|
215
|
+
const [fileSize] = this.unpack(sizeTypeFormat, pageData, offset);
|
|
216
|
+
offset += sizeTypeSize;
|
|
217
|
+
|
|
218
|
+
// Read filename
|
|
219
|
+
const nameEnd = offset + this.buildConfig.objNameLen;
|
|
220
|
+
if (nameEnd <= pageData.length) {
|
|
221
|
+
const nameBytes = pageData.slice(offset, nameEnd);
|
|
222
|
+
// Find null terminator
|
|
223
|
+
const nullPos = nameBytes.indexOf(0);
|
|
224
|
+
const actualNameBytes =
|
|
225
|
+
nullPos !== -1 ? nameBytes.slice(0, nullPos) : nameBytes;
|
|
226
|
+
const filename = new TextDecoder().decode(actualNameBytes);
|
|
227
|
+
|
|
228
|
+
const fileInfo = this.filesMap.get(objId)!;
|
|
229
|
+
fileInfo.name = filename;
|
|
230
|
+
fileInfo.size = fileSize;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
listFiles(): SpiffsFile[] {
|
|
236
|
+
const files: SpiffsFile[] = [];
|
|
237
|
+
|
|
238
|
+
for (const [, fileInfo] of this.filesMap) {
|
|
239
|
+
if (fileInfo.name === null) continue;
|
|
240
|
+
|
|
241
|
+
// Sort data pages by span index
|
|
242
|
+
fileInfo.dataPages.sort((a, b) => a[0] - b[0]);
|
|
243
|
+
|
|
244
|
+
// Reconstruct file content
|
|
245
|
+
const chunks: Uint8Array[] = [];
|
|
246
|
+
let totalWritten = 0;
|
|
247
|
+
|
|
248
|
+
for (const [, content] of fileInfo.dataPages) {
|
|
249
|
+
const remaining = fileInfo.size - totalWritten;
|
|
250
|
+
if (remaining <= 0) break;
|
|
251
|
+
|
|
252
|
+
const toWrite = Math.min(content.length, remaining);
|
|
253
|
+
chunks.push(content.slice(0, toWrite));
|
|
254
|
+
totalWritten += toWrite;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Concatenate chunks
|
|
258
|
+
const data = new Uint8Array(totalWritten);
|
|
259
|
+
let offset = 0;
|
|
260
|
+
for (const chunk of chunks) {
|
|
261
|
+
data.set(chunk, offset);
|
|
262
|
+
offset += chunk.length;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
files.push({
|
|
266
|
+
name: fileInfo.name,
|
|
267
|
+
size: fileInfo.size,
|
|
268
|
+
data,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return files;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
readFile(path: string): Uint8Array | null {
|
|
276
|
+
const files = this.listFiles();
|
|
277
|
+
const file = files.find((f) => f.name === path || f.name === "/" + path);
|
|
278
|
+
return file ? file.data : null;
|
|
279
|
+
}
|
|
280
|
+
}
|