Skip to content

Commit fb9d7d8

Browse files
committed
nvs blob upload
1 parent 119a629 commit fb9d7d8

1 file changed

Lines changed: 154 additions & 0 deletions

File tree

js/nvs-editor.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,7 @@ export class NVSEditor {
16411641
<div><strong>Status:</strong> <span class="${status === "OK" ? "nvs-ok" : "nvs-warn"}">${status}</span></div>
16421642
<button class="nvs-blob-dump" data-key="${this._esc(id)}" title="Show hex dump">📄 Hex Dump</button>
16431643
<button class="nvs-blob-download" data-key="${this._esc(id)}" title="Download blob">⬇️ Download</button>
1644+
<button class="nvs-blob-upload" data-key="${this._esc(id)}" title="Upload / replace blob from file">⬆️ Upload</button>
16441645
</div>`;
16451646
}
16461647
} else {
@@ -1735,5 +1736,158 @@ export class NVSEditor {
17351736
}, 0);
17361737
});
17371738
});
1739+
1740+
// Blob upload buttons — replace blob payload from a local file
1741+
dialogContainer.querySelectorAll(".nvs-blob-upload").forEach((btn) => {
1742+
btn.addEventListener("click", () => {
1743+
const id = btn.dataset.key;
1744+
const blob = blobs.get(id);
1745+
if (!blob) return;
1746+
1747+
const input = document.createElement("input");
1748+
input.type = "file";
1749+
input.style.display = "none";
1750+
document.body.appendChild(input);
1751+
1752+
input.addEventListener("change", async () => {
1753+
const file = input.files && input.files[0];
1754+
input.remove();
1755+
if (!file) return;
1756+
1757+
try {
1758+
const buf = await file.arrayBuffer();
1759+
const bytes = new Uint8Array(buf);
1760+
1761+
if (
1762+
!confirm(
1763+
`Replace blob "${blob.namespace}::${blob.key}" ` +
1764+
`(current ${blob.totalSize} bytes) with ${bytes.length} bytes ` +
1765+
`from "${file.name}"?`,
1766+
)
1767+
)
1768+
return;
1769+
1770+
this._uploadBlobToNVS(blob, bytes);
1771+
1772+
// Re-render the dialog to reflect the new blob state
1773+
dialogContainer.remove();
1774+
this._showBlobs();
1775+
} catch (e) {
1776+
alert("Upload failed: " + (e && e.message ? e.message : e));
1777+
}
1778+
});
1779+
1780+
input.click();
1781+
});
1782+
});
1783+
}
1784+
1785+
/**
1786+
* Replace the data of an existing blob in the NVS partition with `fileBytes`.
1787+
* Distributes bytes across the existing data-chunk slots (0x42 entries),
1788+
* updates each entry's size + data-CRC + header-CRC, and refreshes the
1789+
* blob_index totalSize (0x48) when present. Will not allocate new chunks
1790+
* or extend the partition; the new payload must fit in the existing slots.
1791+
*/
1792+
_uploadBlobToNVS(blob, fileBytes) {
1793+
// Resolve the parsed data-chunk items (in chunk-index order) so we know
1794+
// each slot's offset/span and can size-check before mutating anything.
1795+
const sortedChunks = [...blob.chunks].sort((a, b) => a.index - b.index);
1796+
if (sortedChunks.length === 0) {
1797+
throw new Error("No data chunks found for this blob.");
1798+
}
1799+
1800+
const dataEntries = [];
1801+
for (const chunk of sortedChunks) {
1802+
// chunk.offset points to the payload; the entry header is the 32 bytes before.
1803+
const entryOff = chunk.offset - 32;
1804+
const item = this._findItem(entryOff);
1805+
if (!item || item.datatype !== 0x42) {
1806+
throw new Error(
1807+
`Could not locate data entry at 0x${entryOff.toString(16)}.`,
1808+
);
1809+
}
1810+
// span 1 = inline (no payload extension); capacity is (span-1)*32 bytes.
1811+
const maxSize = (item.span - 1) * 32;
1812+
dataEntries.push({ item, maxSize });
1813+
}
1814+
1815+
const totalCapacity = dataEntries.reduce((s, e) => s + e.maxSize, 0);
1816+
if (fileBytes.length > totalCapacity) {
1817+
throw new Error(
1818+
`File too large: ${fileBytes.length} bytes, but only ${totalCapacity} bytes ` +
1819+
`available across ${dataEntries.length} chunk slot(s). ` +
1820+
`Adding new chunks is not supported.`,
1821+
);
1822+
}
1823+
1824+
// Distribute payload across chunks sequentially.
1825+
let writeOffset = 0;
1826+
for (const { item, maxSize } of dataEntries) {
1827+
const off = item.offset;
1828+
const dataOff = off + 32;
1829+
const remaining = fileBytes.length - writeOffset;
1830+
const writeLen = Math.min(remaining, maxSize);
1831+
1832+
// Wipe the old payload area, then write the new segment.
1833+
this.data.fill(0xff, dataOff, dataOff + maxSize);
1834+
if (writeLen > 0) {
1835+
this.data.set(
1836+
fileBytes.subarray(writeOffset, writeOffset + writeLen),
1837+
dataOff,
1838+
);
1839+
}
1840+
1841+
// Update size field at +24 (u16) — the high two bytes are unused (0xFF).
1842+
this.data[off + 24] = writeLen & 0xff;
1843+
this.data[off + 25] = (writeLen >> 8) & 0xff;
1844+
this.data[off + 26] = 0xff;
1845+
this.data[off + 27] = 0xff;
1846+
1847+
// Update data CRC at +28 (u32 little-endian).
1848+
const segData = this.data.subarray(dataOff, dataOff + writeLen);
1849+
const dataCrc = NVSEditor.crc32(segData, 0, writeLen);
1850+
const dv = new DataView(
1851+
this.data.buffer,
1852+
this.data.byteOffset + off + 28,
1853+
4,
1854+
);
1855+
dv.setUint32(0, dataCrc >>> 0, true);
1856+
1857+
// Recalculate header CRC at +4.
1858+
const hcrc = NVSEditor.crc32Header(this.data, off);
1859+
const hdv = new DataView(
1860+
this.data.buffer,
1861+
this.data.byteOffset + off + 4,
1862+
4,
1863+
);
1864+
hdv.setUint32(0, hcrc >>> 0, true);
1865+
1866+
writeOffset += writeLen;
1867+
}
1868+
1869+
// If a blob_index (0x48) entry exists, sync its totalSize at +24 (u32) and
1870+
// recalc its header CRC. chunkCount stays the same — we did not add slots.
1871+
if (blob.indexEntry && blob.indexEntry.datatype === 0x48) {
1872+
const idxOff = blob.indexEntry.offset;
1873+
const dv = new DataView(
1874+
this.data.buffer,
1875+
this.data.byteOffset + idxOff + 24,
1876+
4,
1877+
);
1878+
dv.setUint32(0, fileBytes.length >>> 0, true);
1879+
const hcrc = NVSEditor.crc32Header(this.data, idxOff);
1880+
const hdv = new DataView(
1881+
this.data.buffer,
1882+
this.data.byteOffset + idxOff + 4,
1883+
4,
1884+
);
1885+
hdv.setUint32(0, hcrc >>> 0, true);
1886+
}
1887+
1888+
this.modified = true;
1889+
this.pages = this._parse();
1890+
this._renderContent();
1891+
this._updateWriteButton();
17381892
}
17391893
}

0 commit comments

Comments
 (0)