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,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPIFFS Page Classes
|
|
3
|
+
* Based on ESP-IDF spiffsgen.py
|
|
4
|
+
*/
|
|
5
|
+
import { SpiffsFullError, SPIFFS_PH_FLAG_USED_FINAL_INDEX, SPIFFS_PH_FLAG_USED_FINAL, SPIFFS_TYPE_FILE, } from "./spiffsConfig";
|
|
6
|
+
export class SpiffsPage {
|
|
7
|
+
constructor(bix, buildConfig) {
|
|
8
|
+
this.buildConfig = buildConfig;
|
|
9
|
+
this.bix = bix;
|
|
10
|
+
}
|
|
11
|
+
pack(format, ...values) {
|
|
12
|
+
const buffer = new ArrayBuffer(this.calcSize(format));
|
|
13
|
+
const view = new DataView(buffer);
|
|
14
|
+
let offset = 0;
|
|
15
|
+
for (let i = 0; i < format.length; i++) {
|
|
16
|
+
const type = format[i];
|
|
17
|
+
const value = values[i];
|
|
18
|
+
switch (type) {
|
|
19
|
+
case "B": // unsigned char (1 byte)
|
|
20
|
+
view.setUint8(offset, value);
|
|
21
|
+
offset += 1;
|
|
22
|
+
break;
|
|
23
|
+
case "H": // unsigned short (2 bytes)
|
|
24
|
+
if (this.buildConfig.endianness === "little") {
|
|
25
|
+
view.setUint16(offset, value, true);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
view.setUint16(offset, value, false);
|
|
29
|
+
}
|
|
30
|
+
offset += 2;
|
|
31
|
+
break;
|
|
32
|
+
case "I": // unsigned int (4 bytes)
|
|
33
|
+
if (this.buildConfig.endianness === "little") {
|
|
34
|
+
view.setUint32(offset, value, true);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
view.setUint32(offset, value, false);
|
|
38
|
+
}
|
|
39
|
+
offset += 4;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return new Uint8Array(buffer);
|
|
44
|
+
}
|
|
45
|
+
unpack(format, data, offset = 0) {
|
|
46
|
+
const view = new DataView(data.buffer, data.byteOffset + offset);
|
|
47
|
+
const results = [];
|
|
48
|
+
let pos = 0;
|
|
49
|
+
for (const type of format) {
|
|
50
|
+
switch (type) {
|
|
51
|
+
case "B":
|
|
52
|
+
results.push(view.getUint8(pos));
|
|
53
|
+
pos += 1;
|
|
54
|
+
break;
|
|
55
|
+
case "H":
|
|
56
|
+
results.push(this.buildConfig.endianness === "little"
|
|
57
|
+
? view.getUint16(pos, true)
|
|
58
|
+
: view.getUint16(pos, false));
|
|
59
|
+
pos += 2;
|
|
60
|
+
break;
|
|
61
|
+
case "I":
|
|
62
|
+
results.push(this.buildConfig.endianness === "little"
|
|
63
|
+
? view.getUint32(pos, true)
|
|
64
|
+
: view.getUint32(pos, false));
|
|
65
|
+
pos += 4;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
71
|
+
calcSize(format) {
|
|
72
|
+
let size = 0;
|
|
73
|
+
for (const type of format) {
|
|
74
|
+
switch (type) {
|
|
75
|
+
case "B":
|
|
76
|
+
size += 1;
|
|
77
|
+
break;
|
|
78
|
+
case "H":
|
|
79
|
+
size += 2;
|
|
80
|
+
break;
|
|
81
|
+
case "I":
|
|
82
|
+
size += 4;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return size;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export class SpiffsObjPageWithIdx extends SpiffsPage {
|
|
90
|
+
constructor(objId, buildConfig) {
|
|
91
|
+
super(0, buildConfig);
|
|
92
|
+
this.objId = objId;
|
|
93
|
+
}
|
|
94
|
+
getObjId() {
|
|
95
|
+
return this.objId;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export class SpiffsObjLuPage extends SpiffsPage {
|
|
99
|
+
constructor(bix, buildConfig) {
|
|
100
|
+
super(bix, buildConfig);
|
|
101
|
+
this.objIdsLimit = this.buildConfig.OBJ_LU_PAGES_OBJ_IDS_LIM;
|
|
102
|
+
this.objIds = [];
|
|
103
|
+
}
|
|
104
|
+
calcMagic(blocksLim) {
|
|
105
|
+
let magic = 0x20140529 ^ this.buildConfig.pageSize;
|
|
106
|
+
if (this.buildConfig.useMagicLen) {
|
|
107
|
+
magic = magic ^ (blocksLim - this.bix);
|
|
108
|
+
}
|
|
109
|
+
const mask = (1 << (8 * this.buildConfig.objIdLen)) - 1;
|
|
110
|
+
return magic & mask;
|
|
111
|
+
}
|
|
112
|
+
registerPage(page) {
|
|
113
|
+
if (this.objIdsLimit <= 0) {
|
|
114
|
+
throw new SpiffsFullError();
|
|
115
|
+
}
|
|
116
|
+
const pageType = page instanceof SpiffsObjIndexPage ? "index" : "data";
|
|
117
|
+
this.objIds.push([page.getObjId(), pageType]);
|
|
118
|
+
this.objIdsLimit--;
|
|
119
|
+
}
|
|
120
|
+
toBinary() {
|
|
121
|
+
const img = new Uint8Array(this.buildConfig.pageSize);
|
|
122
|
+
img.fill(0xff);
|
|
123
|
+
let offset = 0;
|
|
124
|
+
for (const [objId, pageType] of this.objIds) {
|
|
125
|
+
let id = objId;
|
|
126
|
+
if (pageType === "index") {
|
|
127
|
+
id ^= 1 << (this.buildConfig.objIdLen * 8 - 1);
|
|
128
|
+
}
|
|
129
|
+
const packed = this.pack(this.buildConfig.objIdLen === 1
|
|
130
|
+
? "B"
|
|
131
|
+
: this.buildConfig.objIdLen === 2
|
|
132
|
+
? "H"
|
|
133
|
+
: "I", id);
|
|
134
|
+
img.set(packed, offset);
|
|
135
|
+
offset += packed.length;
|
|
136
|
+
}
|
|
137
|
+
return img;
|
|
138
|
+
}
|
|
139
|
+
magicfy(blocksLim) {
|
|
140
|
+
const remaining = this.objIdsLimit;
|
|
141
|
+
const emptyObjId = (1 << (this.buildConfig.objIdLen * 8)) - 1;
|
|
142
|
+
if (remaining >= 2) {
|
|
143
|
+
for (let i = 0; i < remaining; i++) {
|
|
144
|
+
if (i === remaining - 2) {
|
|
145
|
+
this.objIds.push([this.calcMagic(blocksLim), "data"]);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
this.objIds.push([emptyObjId, "data"]);
|
|
150
|
+
}
|
|
151
|
+
this.objIdsLimit--;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export class SpiffsObjIndexPage extends SpiffsObjPageWithIdx {
|
|
157
|
+
constructor(objId, spanIx, size, name, buildConfig) {
|
|
158
|
+
super(objId, buildConfig);
|
|
159
|
+
this.spanIx = spanIx;
|
|
160
|
+
this.name = name;
|
|
161
|
+
this.size = size;
|
|
162
|
+
if (this.spanIx === 0) {
|
|
163
|
+
this.pagesLim = this.buildConfig.OBJ_INDEX_PAGES_OBJ_IDS_HEAD_LIM;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
this.pagesLim = this.buildConfig.OBJ_INDEX_PAGES_OBJ_IDS_LIM;
|
|
167
|
+
}
|
|
168
|
+
this.pages = [];
|
|
169
|
+
}
|
|
170
|
+
registerPage(page) {
|
|
171
|
+
if (this.pagesLim <= 0) {
|
|
172
|
+
throw new SpiffsFullError();
|
|
173
|
+
}
|
|
174
|
+
this.pages.push(page.offset);
|
|
175
|
+
this.pagesLim--;
|
|
176
|
+
}
|
|
177
|
+
toBinary() {
|
|
178
|
+
const img = new Uint8Array(this.buildConfig.pageSize);
|
|
179
|
+
img.fill(0xff);
|
|
180
|
+
const objId = this.objId ^ (1 << (this.buildConfig.objIdLen * 8 - 1));
|
|
181
|
+
const format = (this.buildConfig.objIdLen === 1
|
|
182
|
+
? "B"
|
|
183
|
+
: this.buildConfig.objIdLen === 2
|
|
184
|
+
? "H"
|
|
185
|
+
: "I") +
|
|
186
|
+
(this.buildConfig.spanIxLen === 1
|
|
187
|
+
? "B"
|
|
188
|
+
: this.buildConfig.spanIxLen === 2
|
|
189
|
+
? "H"
|
|
190
|
+
: "I") +
|
|
191
|
+
"B";
|
|
192
|
+
let offset = 0;
|
|
193
|
+
const header = this.pack(format, objId, this.spanIx, SPIFFS_PH_FLAG_USED_FINAL_INDEX);
|
|
194
|
+
img.set(header, offset);
|
|
195
|
+
offset += header.length;
|
|
196
|
+
// Add padding
|
|
197
|
+
offset += this.buildConfig.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED_PAD;
|
|
198
|
+
// If first index page, add filename, type and size
|
|
199
|
+
if (this.spanIx === 0) {
|
|
200
|
+
const sizeType = this.pack("IB", this.size, SPIFFS_TYPE_FILE);
|
|
201
|
+
img.set(sizeType, offset);
|
|
202
|
+
offset += sizeType.length;
|
|
203
|
+
// Write filename with proper null-termination
|
|
204
|
+
const nameBytes = new TextEncoder().encode(this.name);
|
|
205
|
+
// Ensure we don't exceed objNameLen
|
|
206
|
+
const bytesToWrite = Math.min(nameBytes.length, this.buildConfig.objNameLen);
|
|
207
|
+
img.set(nameBytes.slice(0, bytesToWrite), offset);
|
|
208
|
+
// The rest is already 0xFF from img.fill(0xff), but SPIFFS expects 0x00 for unused name bytes
|
|
209
|
+
// Fill remaining name bytes with 0x00
|
|
210
|
+
for (let i = bytesToWrite; i < this.buildConfig.objNameLen; i++) {
|
|
211
|
+
img[offset + i] = 0x00;
|
|
212
|
+
}
|
|
213
|
+
offset +=
|
|
214
|
+
this.buildConfig.objNameLen +
|
|
215
|
+
this.buildConfig.metaLen +
|
|
216
|
+
this.buildConfig.OBJ_INDEX_PAGES_HEADER_LEN_ALIGNED_PAD;
|
|
217
|
+
}
|
|
218
|
+
// Add page indices
|
|
219
|
+
for (const page of this.pages) {
|
|
220
|
+
// Calculate page index by dividing page offset by page size
|
|
221
|
+
// pageSize is always a power of 2, so integer division is safe
|
|
222
|
+
const pageIx = Math.floor(page / this.buildConfig.pageSize);
|
|
223
|
+
const pageIxPacked = this.pack(this.buildConfig.pageIxLen === 1
|
|
224
|
+
? "B"
|
|
225
|
+
: this.buildConfig.pageIxLen === 2
|
|
226
|
+
? "H"
|
|
227
|
+
: "I", pageIx);
|
|
228
|
+
img.set(pageIxPacked, offset);
|
|
229
|
+
offset += pageIxPacked.length;
|
|
230
|
+
}
|
|
231
|
+
return img;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
export class SpiffsObjDataPage extends SpiffsObjPageWithIdx {
|
|
235
|
+
constructor(offset, objId, spanIx, contents, buildConfig) {
|
|
236
|
+
super(objId, buildConfig);
|
|
237
|
+
this.offset = offset;
|
|
238
|
+
this.spanIx = spanIx;
|
|
239
|
+
this.contents = contents;
|
|
240
|
+
}
|
|
241
|
+
toBinary() {
|
|
242
|
+
const img = new Uint8Array(this.buildConfig.pageSize);
|
|
243
|
+
img.fill(0xff);
|
|
244
|
+
const format = (this.buildConfig.objIdLen === 1
|
|
245
|
+
? "B"
|
|
246
|
+
: this.buildConfig.objIdLen === 2
|
|
247
|
+
? "H"
|
|
248
|
+
: "I") +
|
|
249
|
+
(this.buildConfig.spanIxLen === 1
|
|
250
|
+
? "B"
|
|
251
|
+
: this.buildConfig.spanIxLen === 2
|
|
252
|
+
? "H"
|
|
253
|
+
: "I") +
|
|
254
|
+
"B";
|
|
255
|
+
const header = this.pack(format, this.objId, this.spanIx, SPIFFS_PH_FLAG_USED_FINAL);
|
|
256
|
+
img.set(header, 0);
|
|
257
|
+
img.set(this.contents, header.length);
|
|
258
|
+
return img;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPIFFS Reader - Parse and extract files from SPIFFS images
|
|
3
|
+
* Based on ESP-IDF spiffsgen.py extract_files() method
|
|
4
|
+
*/
|
|
5
|
+
import { SpiffsBuildConfig } from "./spiffsConfig";
|
|
6
|
+
import type { SpiffsFile } from "./spiffs";
|
|
7
|
+
export declare class SpiffsReader {
|
|
8
|
+
private buildConfig;
|
|
9
|
+
private imageData;
|
|
10
|
+
private filesMap;
|
|
11
|
+
constructor(imageData: Uint8Array, buildConfig: SpiffsBuildConfig);
|
|
12
|
+
private unpack;
|
|
13
|
+
parse(): void;
|
|
14
|
+
private parseBlock;
|
|
15
|
+
private parsePage;
|
|
16
|
+
private parseIndexPage;
|
|
17
|
+
listFiles(): SpiffsFile[];
|
|
18
|
+
readFile(path: string): Uint8Array | null;
|
|
19
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPIFFS Reader - Parse and extract files from SPIFFS images
|
|
3
|
+
* Based on ESP-IDF spiffsgen.py extract_files() method
|
|
4
|
+
*/
|
|
5
|
+
import { SPIFFS_PH_FLAG_USED_FINAL_INDEX, SPIFFS_PH_FLAG_USED_FINAL, SPIFFS_PH_FLAG_LEN, SPIFFS_PH_IX_SIZE_LEN, SPIFFS_PH_IX_OBJ_TYPE_LEN, } from "./spiffsConfig";
|
|
6
|
+
export class SpiffsReader {
|
|
7
|
+
constructor(imageData, buildConfig) {
|
|
8
|
+
this.imageData = imageData;
|
|
9
|
+
this.buildConfig = buildConfig;
|
|
10
|
+
this.filesMap = new Map();
|
|
11
|
+
}
|
|
12
|
+
unpack(format, data, offset = 0) {
|
|
13
|
+
const view = new DataView(data.buffer, data.byteOffset + offset);
|
|
14
|
+
const results = [];
|
|
15
|
+
let pos = 0;
|
|
16
|
+
for (const type of format) {
|
|
17
|
+
switch (type) {
|
|
18
|
+
case "B":
|
|
19
|
+
results.push(view.getUint8(pos));
|
|
20
|
+
pos += 1;
|
|
21
|
+
break;
|
|
22
|
+
case "H":
|
|
23
|
+
results.push(this.buildConfig.endianness === "little"
|
|
24
|
+
? view.getUint16(pos, true)
|
|
25
|
+
: view.getUint16(pos, false));
|
|
26
|
+
pos += 2;
|
|
27
|
+
break;
|
|
28
|
+
case "I":
|
|
29
|
+
results.push(this.buildConfig.endianness === "little"
|
|
30
|
+
? view.getUint32(pos, true)
|
|
31
|
+
: view.getUint32(pos, false));
|
|
32
|
+
pos += 4;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return results;
|
|
37
|
+
}
|
|
38
|
+
parse() {
|
|
39
|
+
const blocksCount = Math.floor(this.imageData.length / this.buildConfig.blockSize);
|
|
40
|
+
for (let bix = 0; bix < blocksCount; bix++) {
|
|
41
|
+
const blockOffset = bix * this.buildConfig.blockSize;
|
|
42
|
+
const blockData = this.imageData.slice(blockOffset, blockOffset + this.buildConfig.blockSize);
|
|
43
|
+
this.parseBlock(blockData);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
parseBlock(blockData) {
|
|
47
|
+
// Parse lookup pages to find valid objects
|
|
48
|
+
for (let pageIdx = 0; pageIdx < this.buildConfig.OBJ_LU_PAGES_PER_BLOCK; pageIdx++) {
|
|
49
|
+
const luPageOffset = pageIdx * this.buildConfig.pageSize;
|
|
50
|
+
const luPageData = blockData.slice(luPageOffset, luPageOffset + this.buildConfig.pageSize);
|
|
51
|
+
// Parse object IDs from lookup page
|
|
52
|
+
for (let i = 0; i < luPageData.length; i += this.buildConfig.objIdLen) {
|
|
53
|
+
if (i + this.buildConfig.objIdLen > luPageData.length)
|
|
54
|
+
break;
|
|
55
|
+
const objIdBytes = luPageData.slice(i, i + this.buildConfig.objIdLen);
|
|
56
|
+
const [objId] = this.unpack(this.buildConfig.objIdLen === 1
|
|
57
|
+
? "B"
|
|
58
|
+
: this.buildConfig.objIdLen === 2
|
|
59
|
+
? "H"
|
|
60
|
+
: "I", objIdBytes);
|
|
61
|
+
// Check if it's a valid object (not erased/empty)
|
|
62
|
+
const emptyValue = (1 << (this.buildConfig.objIdLen * 8)) - 1;
|
|
63
|
+
if (objId === emptyValue)
|
|
64
|
+
continue;
|
|
65
|
+
// Check if it's an index page (MSB set)
|
|
66
|
+
const isIndex = (objId & (1 << (this.buildConfig.objIdLen * 8 - 1))) !== 0;
|
|
67
|
+
const realObjId = objId & ~(1 << (this.buildConfig.objIdLen * 8 - 1));
|
|
68
|
+
if (isIndex && !this.filesMap.has(realObjId)) {
|
|
69
|
+
this.filesMap.set(realObjId, {
|
|
70
|
+
name: null,
|
|
71
|
+
size: 0,
|
|
72
|
+
dataPages: [],
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Parse actual pages to get file metadata and content
|
|
78
|
+
for (let pageIdx = this.buildConfig.OBJ_LU_PAGES_PER_BLOCK; pageIdx < this.buildConfig.PAGES_PER_BLOCK; pageIdx++) {
|
|
79
|
+
const pageOffset = pageIdx * this.buildConfig.pageSize;
|
|
80
|
+
const pageData = blockData.slice(pageOffset, pageOffset + this.buildConfig.pageSize);
|
|
81
|
+
this.parsePage(pageData);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
parsePage(pageData) {
|
|
85
|
+
// Parse page header
|
|
86
|
+
const headerFormat = (this.buildConfig.objIdLen === 1
|
|
87
|
+
? "B"
|
|
88
|
+
: this.buildConfig.objIdLen === 2
|
|
89
|
+
? "H"
|
|
90
|
+
: "I") +
|
|
91
|
+
(this.buildConfig.spanIxLen === 1
|
|
92
|
+
? "B"
|
|
93
|
+
: this.buildConfig.spanIxLen === 2
|
|
94
|
+
? "H"
|
|
95
|
+
: "I") +
|
|
96
|
+
"B";
|
|
97
|
+
const headerSize = this.buildConfig.objIdLen +
|
|
98
|
+
this.buildConfig.spanIxLen +
|
|
99
|
+
SPIFFS_PH_FLAG_LEN;
|
|
100
|
+
if (pageData.length < headerSize)
|
|
101
|
+
return;
|
|
102
|
+
const [objId, spanIx, flags] = this.unpack(headerFormat, pageData);
|
|
103
|
+
// Check for valid page
|
|
104
|
+
const emptyId = (1 << (this.buildConfig.objIdLen * 8)) - 1;
|
|
105
|
+
if (objId === emptyId)
|
|
106
|
+
return;
|
|
107
|
+
const isIndex = (objId & (1 << (this.buildConfig.objIdLen * 8 - 1))) !== 0;
|
|
108
|
+
const realObjId = objId & ~(1 << (this.buildConfig.objIdLen * 8 - 1));
|
|
109
|
+
if (isIndex && flags === SPIFFS_PH_FLAG_USED_FINAL_INDEX) {
|
|
110
|
+
// Index page - contains file metadata
|
|
111
|
+
if (!this.filesMap.has(realObjId)) {
|
|
112
|
+
this.filesMap.set(realObjId, {
|
|
113
|
+
name: null,
|
|
114
|
+
size: 0,
|
|
115
|
+
dataPages: [],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// Only first index page (span_ix == 0) has filename and size
|
|
119
|
+
if (spanIx === 0) {
|
|
120
|
+
this.parseIndexPage(pageData, headerSize, realObjId);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else if (!isIndex && flags === SPIFFS_PH_FLAG_USED_FINAL) {
|
|
124
|
+
// Data page - contains file content
|
|
125
|
+
if (this.filesMap.has(realObjId)) {
|
|
126
|
+
const contentStart = headerSize;
|
|
127
|
+
const content = pageData.slice(contentStart, contentStart + this.buildConfig.OBJ_DATA_PAGE_CONTENT_LEN);
|
|
128
|
+
this.filesMap.get(realObjId).dataPages.push([spanIx, content]);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
parseIndexPage(pageData, headerSize, objId) {
|
|
133
|
+
// Skip to size and type fields
|
|
134
|
+
let offset = headerSize + this.buildConfig.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED_PAD;
|
|
135
|
+
const sizeTypeFormat = "IB";
|
|
136
|
+
const sizeTypeSize = SPIFFS_PH_IX_SIZE_LEN + SPIFFS_PH_IX_OBJ_TYPE_LEN;
|
|
137
|
+
if (offset + sizeTypeSize <= pageData.length) {
|
|
138
|
+
const [fileSize] = this.unpack(sizeTypeFormat, pageData, offset);
|
|
139
|
+
offset += sizeTypeSize;
|
|
140
|
+
// Read filename
|
|
141
|
+
const nameEnd = offset + this.buildConfig.objNameLen;
|
|
142
|
+
if (nameEnd <= pageData.length) {
|
|
143
|
+
const nameBytes = pageData.slice(offset, nameEnd);
|
|
144
|
+
// Find null terminator
|
|
145
|
+
const nullPos = nameBytes.indexOf(0);
|
|
146
|
+
const actualNameBytes = nullPos !== -1 ? nameBytes.slice(0, nullPos) : nameBytes;
|
|
147
|
+
const filename = new TextDecoder().decode(actualNameBytes);
|
|
148
|
+
const fileInfo = this.filesMap.get(objId);
|
|
149
|
+
fileInfo.name = filename;
|
|
150
|
+
fileInfo.size = fileSize;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
listFiles() {
|
|
155
|
+
const files = [];
|
|
156
|
+
for (const [, fileInfo] of this.filesMap) {
|
|
157
|
+
if (fileInfo.name === null)
|
|
158
|
+
continue;
|
|
159
|
+
// Sort data pages by span index
|
|
160
|
+
fileInfo.dataPages.sort((a, b) => a[0] - b[0]);
|
|
161
|
+
// Reconstruct file content
|
|
162
|
+
const chunks = [];
|
|
163
|
+
let totalWritten = 0;
|
|
164
|
+
for (const [, content] of fileInfo.dataPages) {
|
|
165
|
+
const remaining = fileInfo.size - totalWritten;
|
|
166
|
+
if (remaining <= 0)
|
|
167
|
+
break;
|
|
168
|
+
const toWrite = Math.min(content.length, remaining);
|
|
169
|
+
chunks.push(content.slice(0, toWrite));
|
|
170
|
+
totalWritten += toWrite;
|
|
171
|
+
}
|
|
172
|
+
// Concatenate chunks
|
|
173
|
+
const data = new Uint8Array(totalWritten);
|
|
174
|
+
let offset = 0;
|
|
175
|
+
for (const chunk of chunks) {
|
|
176
|
+
data.set(chunk, offset);
|
|
177
|
+
offset += chunk.length;
|
|
178
|
+
}
|
|
179
|
+
files.push({
|
|
180
|
+
name: fileInfo.name,
|
|
181
|
+
size: fileInfo.size,
|
|
182
|
+
data,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return files;
|
|
186
|
+
}
|
|
187
|
+
readFile(path) {
|
|
188
|
+
const files = this.listFiles();
|
|
189
|
+
const file = files.find((f) => f.name === path || f.name === "/" + path);
|
|
190
|
+
return file ? file.data : null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESP32 Partition Table Parser
|
|
3
|
+
* Based on ESP-IDF partition table format
|
|
4
|
+
*/
|
|
5
|
+
export interface Partition {
|
|
6
|
+
name: string;
|
|
7
|
+
type: number;
|
|
8
|
+
subtype: number;
|
|
9
|
+
offset: number;
|
|
10
|
+
size: number;
|
|
11
|
+
flags: number;
|
|
12
|
+
typeName: string;
|
|
13
|
+
subtypeName: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse the entire partition table
|
|
17
|
+
*/
|
|
18
|
+
export declare function parsePartitionTable(data: Uint8Array): Partition[];
|
|
19
|
+
/**
|
|
20
|
+
* Get the default partition table offset
|
|
21
|
+
*/
|
|
22
|
+
export declare function getPartitionTableOffset(): number;
|
|
23
|
+
/**
|
|
24
|
+
* Format size in human-readable format
|
|
25
|
+
*/
|
|
26
|
+
export declare function formatSize(bytes: number): string;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESP32 Partition Table Parser
|
|
3
|
+
* Based on ESP-IDF partition table format
|
|
4
|
+
*/
|
|
5
|
+
// Partition types
|
|
6
|
+
const PARTITION_TYPES = {
|
|
7
|
+
0x00: "app",
|
|
8
|
+
0x01: "data",
|
|
9
|
+
};
|
|
10
|
+
// App subtypes
|
|
11
|
+
const APP_SUBTYPES = {
|
|
12
|
+
0x00: "factory",
|
|
13
|
+
0x10: "ota_0",
|
|
14
|
+
0x11: "ota_1",
|
|
15
|
+
0x12: "ota_2",
|
|
16
|
+
0x13: "ota_3",
|
|
17
|
+
0x14: "ota_4",
|
|
18
|
+
0x15: "ota_5",
|
|
19
|
+
0x16: "ota_6",
|
|
20
|
+
0x17: "ota_7",
|
|
21
|
+
0x18: "ota_8",
|
|
22
|
+
0x19: "ota_9",
|
|
23
|
+
0x1a: "ota_10",
|
|
24
|
+
0x1b: "ota_11",
|
|
25
|
+
0x1c: "ota_12",
|
|
26
|
+
0x1d: "ota_13",
|
|
27
|
+
0x1e: "ota_14",
|
|
28
|
+
0x1f: "ota_15",
|
|
29
|
+
0x20: "test",
|
|
30
|
+
};
|
|
31
|
+
// Data subtypes
|
|
32
|
+
const DATA_SUBTYPES = {
|
|
33
|
+
0x00: "ota",
|
|
34
|
+
0x01: "phy",
|
|
35
|
+
0x02: "nvs",
|
|
36
|
+
0x03: "coredump",
|
|
37
|
+
0x04: "nvs_keys",
|
|
38
|
+
0x05: "efuse",
|
|
39
|
+
0x80: "esphttpd",
|
|
40
|
+
0x81: "fat",
|
|
41
|
+
0x82: "spiffs",
|
|
42
|
+
};
|
|
43
|
+
const PARTITION_TABLE_OFFSET = 0x8000; // Default partition table offset
|
|
44
|
+
const PARTITION_ENTRY_SIZE = 32;
|
|
45
|
+
const PARTITION_MAGIC = 0x50aa;
|
|
46
|
+
/**
|
|
47
|
+
* Parse a single partition entry from binary data
|
|
48
|
+
*/
|
|
49
|
+
function parsePartitionEntry(data) {
|
|
50
|
+
if (data.length < PARTITION_ENTRY_SIZE) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
// Check magic bytes
|
|
54
|
+
const magic = (data[0] | (data[1] << 8)) & 0xffff;
|
|
55
|
+
if (magic !== PARTITION_MAGIC) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const type = data[2];
|
|
59
|
+
const subtype = data[3];
|
|
60
|
+
const offset = data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24);
|
|
61
|
+
const size = data[8] | (data[9] << 8) | (data[10] << 16) | (data[11] << 24);
|
|
62
|
+
// Name is at offset 12, max 16 bytes, null-terminated
|
|
63
|
+
let name = "";
|
|
64
|
+
for (let i = 12; i < 28; i++) {
|
|
65
|
+
if (data[i] === 0)
|
|
66
|
+
break;
|
|
67
|
+
name += String.fromCharCode(data[i]);
|
|
68
|
+
}
|
|
69
|
+
const flags = data[28] | (data[29] << 8) | (data[30] << 16) | (data[31] << 24);
|
|
70
|
+
// Get type and subtype names
|
|
71
|
+
const typeName = PARTITION_TYPES[type] || `unknown(0x${type.toString(16)})`;
|
|
72
|
+
let subtypeName = "";
|
|
73
|
+
if (type === 0x00) {
|
|
74
|
+
subtypeName = APP_SUBTYPES[subtype] || `unknown(0x${subtype.toString(16)})`;
|
|
75
|
+
}
|
|
76
|
+
else if (type === 0x01) {
|
|
77
|
+
subtypeName =
|
|
78
|
+
DATA_SUBTYPES[subtype] || `unknown(0x${subtype.toString(16)})`;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
subtypeName = `0x${subtype.toString(16)}`;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
name,
|
|
85
|
+
type,
|
|
86
|
+
subtype,
|
|
87
|
+
offset,
|
|
88
|
+
size,
|
|
89
|
+
flags,
|
|
90
|
+
typeName,
|
|
91
|
+
subtypeName,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Parse the entire partition table
|
|
96
|
+
*/
|
|
97
|
+
export function parsePartitionTable(data) {
|
|
98
|
+
const partitions = [];
|
|
99
|
+
for (let i = 0; i < data.length; i += PARTITION_ENTRY_SIZE) {
|
|
100
|
+
const entryData = data.slice(i, i + PARTITION_ENTRY_SIZE);
|
|
101
|
+
const partition = parsePartitionEntry(entryData);
|
|
102
|
+
if (partition === null) {
|
|
103
|
+
// End of partition table or invalid entry
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
partitions.push(partition);
|
|
107
|
+
}
|
|
108
|
+
return partitions;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the default partition table offset
|
|
112
|
+
*/
|
|
113
|
+
export function getPartitionTableOffset() {
|
|
114
|
+
return PARTITION_TABLE_OFFSET;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Format size in human-readable format
|
|
118
|
+
*/
|
|
119
|
+
export function formatSize(bytes) {
|
|
120
|
+
if (bytes < 1024) {
|
|
121
|
+
return `${bytes} B`;
|
|
122
|
+
}
|
|
123
|
+
else if (bytes < 1024 * 1024) {
|
|
124
|
+
return `${(bytes / 1024).toFixed(2)} KB`;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
128
|
+
}
|
|
129
|
+
}
|
package/dist/struct.d.ts
ADDED