Website for binary patching bootloaders
This commit is contained in:
parent
6b57b35d16
commit
206825a0ef
3 changed files with 651 additions and 0 deletions
54
patcher/index.html
Normal file
54
patcher/index.html
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<title>CF2 binary patcher</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#157878">
|
||||
<link rel="stylesheet" href="https://microsoft.github.io/uf2/assets/css/style.css">
|
||||
<script src="patcher.js"></script>
|
||||
<script src="web.js"></script>
|
||||
<style>
|
||||
#patch {
|
||||
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
font-size: 0.9rem;
|
||||
width: 100%;
|
||||
height: 12em;
|
||||
}
|
||||
.page-header {
|
||||
padding: 2rem 2rem !important;
|
||||
}
|
||||
.btn-dl, .btn-dl:hover {
|
||||
background-color: green;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section class="page-header" ondrop="dropHandler(event);" ondragover="dragOverHandler(event);">
|
||||
<h1 class="project-name">CF2 patcher</h1>
|
||||
<h2 class="project-tagline">Drop UF2 file here to binary patch it with your config</h2>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="main-content">
|
||||
<h1>Your patch</h1>
|
||||
<textarea id="patch" spellcheck=false onchange="savePatch(event);"></textarea>
|
||||
<a href="#" id="apply" class="btn btn-dl">Apply my patch</a>
|
||||
|
||||
<h1>Current config</h1>
|
||||
<pre><code id="currconfig">Drop UF2 file above to see its config.</code></pre>
|
||||
|
||||
</section>
|
||||
|
||||
<script>
|
||||
restorePatch()
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
503
patcher/patcher.js
Normal file
503
patcher/patcher.js
Normal file
|
|
@ -0,0 +1,503 @@
|
|||
"use strict";
|
||||
|
||||
const configKeys = {
|
||||
PIN_ACCELEROMETER_INT: 1,
|
||||
PIN_ACCELEROMETER_SCL: 2,
|
||||
PIN_ACCELEROMETER_SDA: 3,
|
||||
PIN_BTN_A: 4,
|
||||
PIN_BTN_B: 5,
|
||||
PIN_BTN_SLIDE: 6,
|
||||
PIN_DOTSTAR_CLOCK: 7,
|
||||
PIN_DOTSTAR_DATA: 8,
|
||||
PIN_FLASH_CS: 9,
|
||||
PIN_FLASH_MISO: 10,
|
||||
PIN_FLASH_MOSI: 11,
|
||||
PIN_FLASH_SCK: 12,
|
||||
PIN_LED: 13,
|
||||
PIN_LIGHT: 14,
|
||||
PIN_MICROPHONE: 15,
|
||||
PIN_MIC_CLOCK: 16,
|
||||
PIN_MIC_DATA: 17,
|
||||
PIN_MISO: 18,
|
||||
PIN_MOSI: 19,
|
||||
PIN_NEOPIXEL: 20,
|
||||
PIN_RX: 21,
|
||||
PIN_RXLED: 22,
|
||||
PIN_SCK: 23,
|
||||
PIN_SCL: 24,
|
||||
PIN_SDA: 25,
|
||||
PIN_SPEAKER_AMP: 26,
|
||||
PIN_TEMPERATURE: 27,
|
||||
PIN_TX: 28,
|
||||
PIN_TXLED: 29,
|
||||
PIN_IR_OUT: 30,
|
||||
PIN_IR_IN: 31,
|
||||
PIN_DISPLAY_SCK: 32,
|
||||
PIN_DISPLAY_MISO: 33,
|
||||
PIN_DISPLAY_MOSI: 34,
|
||||
PIN_DISPLAY_CS: 35,
|
||||
PIN_DISPLAY_DC: 36,
|
||||
DISPLAY_WIDTH: 37,
|
||||
DISPLAY_HEIGHT: 38,
|
||||
DISPLAY_CFG0: 39,
|
||||
DISPLAY_CFG1: 40,
|
||||
DISPLAY_CFG2: 41,
|
||||
DISPLAY_CFG3: 42,
|
||||
PIN_DISPLAY_RST: 43,
|
||||
PIN_DISPLAY_BL: 44,
|
||||
PIN_SERVO_1: 45,
|
||||
PIN_SERVO_2: 46,
|
||||
PIN_BTN_LEFT: 47,
|
||||
PIN_BTN_RIGHT: 48,
|
||||
PIN_BTN_UP: 49,
|
||||
PIN_BTN_DOWN: 50,
|
||||
PIN_BTN_MENU: 51,
|
||||
PIN_LED_R: 52,
|
||||
PIN_LED_G: 53,
|
||||
PIN_LED_B: 54,
|
||||
PIN_LED1: 55,
|
||||
PIN_LED2: 56,
|
||||
PIN_LED3: 57,
|
||||
PIN_LED4: 58,
|
||||
SPEAKER_VOLUME: 59,
|
||||
PIN_JACK_TX: 60,
|
||||
PIN_JACK_SENSE: 61,
|
||||
PIN_JACK_HPEN: 62,
|
||||
PIN_JACK_BZEN: 63,
|
||||
PIN_JACK_PWREN: 64,
|
||||
PIN_JACK_SND: 65,
|
||||
PIN_JACK_BUSLED: 66,
|
||||
PIN_JACK_COMMLED: 67,
|
||||
PIN_BTNMX_LATCH: 66,
|
||||
PIN_BTNMX_CLOCK: 67,
|
||||
PIN_BTNMX_DATA: 68,
|
||||
PIN_BTN_SOFT_RESET: 69,
|
||||
ACCELEROMETER_TYPE: 70,
|
||||
PIN_A0: 100,
|
||||
PIN_A1: 101,
|
||||
PIN_A2: 102,
|
||||
PIN_A3: 103,
|
||||
PIN_A4: 104,
|
||||
PIN_A5: 105,
|
||||
PIN_A6: 106,
|
||||
PIN_A7: 107,
|
||||
PIN_A8: 108,
|
||||
PIN_A9: 109,
|
||||
PIN_A10: 110,
|
||||
PIN_A11: 111,
|
||||
PIN_A12: 112,
|
||||
PIN_A13: 113,
|
||||
PIN_A14: 114,
|
||||
PIN_A15: 115,
|
||||
PIN_D0: 150,
|
||||
PIN_D1: 151,
|
||||
PIN_D2: 152,
|
||||
PIN_D3: 153,
|
||||
PIN_D4: 154,
|
||||
PIN_D5: 155,
|
||||
PIN_D6: 156,
|
||||
PIN_D7: 157,
|
||||
PIN_D8: 158,
|
||||
PIN_D9: 159,
|
||||
PIN_D10: 160,
|
||||
PIN_D11: 161,
|
||||
PIN_D12: 162,
|
||||
PIN_D13: 163,
|
||||
PIN_D14: 164,
|
||||
PIN_D15: 165,
|
||||
NUM_NEOPIXELS: 200,
|
||||
NUM_DOTSTARS: 201,
|
||||
DEFAULT_BUTTON_MODE: 202,
|
||||
SWD_ENABLED: 203,
|
||||
FLASH_BYTES: 204,
|
||||
RAM_BYTES: 205,
|
||||
SYSTEM_HEAP_BYTES: 206,
|
||||
LOW_MEM_SIMULATION_KB: 207,
|
||||
BOOTLOADER_BOARD_ID: 208,
|
||||
UF2_FAMILY: 209,
|
||||
PINS_PORT_SIZE: 210,
|
||||
}
|
||||
|
||||
const enums = {
|
||||
// these are the same as the default I2C ID
|
||||
ACCELEROMETER_TYPE: {
|
||||
LIS3DH: 0x32,
|
||||
MMA8453: 0x38,
|
||||
FXOS8700: 0x3C,
|
||||
MMA8653: 0x3A,
|
||||
MSA300: 0x4C,
|
||||
},
|
||||
UF2_FAMILY: {
|
||||
ATSAMD21: 0x68ed2b88,
|
||||
ATSAMD51: 0x55114460,
|
||||
NRF52840: 0x1b57745f,
|
||||
STM32F103: 0x5ee21072,
|
||||
STM32F401: 0x57755a57,
|
||||
ATMEGA32: 0x16573617,
|
||||
CYPRESS_FX2: 0x5a18069b,
|
||||
},
|
||||
PINS_PORT_SIZE: {
|
||||
PA_16: 16, // PA00-PA15, PB00-PB15, ... - STM32
|
||||
PA_32: 32, // PA00-PA31, ... - ATSAMD
|
||||
P0_16: 1016, // P0_0-P0_15, P1_0-P1_15, ...
|
||||
P0_32: 1032, // P0_0-P0_32, ... - NRF
|
||||
},
|
||||
DEFAULT_BUTTON_MODE: {
|
||||
ACTIVE_HIGH_PULL_DOWN: 0x11,
|
||||
ACTIVE_HIGH_PULL_UP: 0x21,
|
||||
ACTIVE_HIGH_PULL_NONE: 0x31,
|
||||
ACTIVE_LOW_PULL_DOWN: 0x10,
|
||||
ACTIVE_LOW_PULL_UP: 0x20,
|
||||
ACTIVE_LOW_PULL_NONE: 0x30,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
let infoMsg = ""
|
||||
|
||||
function log(msg) {
|
||||
infoMsg += msg + "\n"
|
||||
console.log(msg)
|
||||
}
|
||||
|
||||
function help() {
|
||||
console.log(`
|
||||
USAGE: node patch-cfg.js file.uf2 [patch.cf2]
|
||||
|
||||
Without .cf2 file, it will parse config in the UF2 file and print it out
|
||||
(in .cf2 format).
|
||||
|
||||
With .cf2 file, it will patch in-place the UF2 file with specified config.
|
||||
`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function readBin(fn) {
|
||||
const fs = require("fs")
|
||||
|
||||
if (!fn) {
|
||||
console.log("Required argument missing.")
|
||||
help()
|
||||
}
|
||||
|
||||
try {
|
||||
return fs.readFileSync(fn)
|
||||
} catch (e) {
|
||||
console.log("Cannot read file '" + fn + "': " + e.message)
|
||||
help()
|
||||
}
|
||||
}
|
||||
const configInvKeys = {}
|
||||
|
||||
const UF2_MAGIC_START0 = 0x0A324655 // "UF2\n"
|
||||
const UF2_MAGIC_START1 = 0x9E5D5157 // Randomly selected
|
||||
const UF2_MAGIC_END = 0x0AB16F30 // Ditto
|
||||
|
||||
const CFG_MAGIC0 = 0x1e9e10f1
|
||||
const CFG_MAGIC1 = 0x20227a79
|
||||
|
||||
function err(msg) {
|
||||
log("Fatal error: " + msg)
|
||||
if (typeof window == "undefined") {
|
||||
process.exit(1)
|
||||
} else {
|
||||
throw new Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
function read32(buf, off) {
|
||||
return (buf[off + 0] | (buf[off + 1] << 8) | (buf[off + 2] << 16) | (buf[off + 3] << 24)) >>> 0
|
||||
}
|
||||
|
||||
function write32(buf, off, v) {
|
||||
buf[off + 0] = v & 0xff
|
||||
buf[off + 1] = (v >> 8) & 0xff
|
||||
buf[off + 2] = (v >> 16) & 0xff
|
||||
buf[off + 3] = (v >> 24) & 0xff
|
||||
}
|
||||
|
||||
|
||||
function readWriteConfig(buf, patch) {
|
||||
let patchPtr = null
|
||||
let origData = []
|
||||
let cfgLen = 0
|
||||
if (patch)
|
||||
patch.push(0, 0)
|
||||
for (let off = 0; off < buf.length; off += 512) {
|
||||
if (read32(buf, off) != UF2_MAGIC_START0 ||
|
||||
read32(buf, off + 4) != UF2_MAGIC_START1) {
|
||||
err("invalid data at " + off)
|
||||
}
|
||||
|
||||
const payloadLen = read32(buf, off + 16)
|
||||
|
||||
for (let i = 32; i < 32 + payloadLen; i += 4) {
|
||||
if (read32(buf, off + i) == CFG_MAGIC0 &&
|
||||
read32(buf, off + i + 4) == CFG_MAGIC1) {
|
||||
let addr = "0x" + (read32(buf, off + 12) + i - 32).toString(16)
|
||||
if (patchPtr === null) {
|
||||
log(`# Found CFG DATA at ${addr}`)
|
||||
patchPtr = -4
|
||||
} else {
|
||||
log(`# Skipping second CFG DATA at ${addr}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (patchPtr !== null) {
|
||||
if (patchPtr == -2) {
|
||||
cfgLen = read32(buf, off + i)
|
||||
if (patch)
|
||||
write32(buf, off + i, (patch.length >> 1) - 1)
|
||||
}
|
||||
|
||||
if (patchPtr >= 0) {
|
||||
if (origData.length < cfgLen * 2 + 2)
|
||||
origData.push(read32(buf, off + i))
|
||||
if (patch) {
|
||||
if (patchPtr < patch.length) {
|
||||
write32(buf, off + i, patch[patchPtr])
|
||||
}
|
||||
}
|
||||
}
|
||||
patchPtr++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (origData.length == 0)
|
||||
err("config data not found")
|
||||
if (patch && patchPtr < patch.length)
|
||||
err("no space for config data")
|
||||
if (origData.slice(origData.length - 2).some(x => x != 0))
|
||||
err("config data not zero terminated")
|
||||
origData = origData.slice(0, origData.length - 2)
|
||||
return origData
|
||||
}
|
||||
|
||||
|
||||
function lookupCfg(cfgdata, key) {
|
||||
for (let i = 0; i < cfgdata.length; i += 2)
|
||||
if (cfgdata[i] == key)
|
||||
return cfgdata[i + 1]
|
||||
return null
|
||||
}
|
||||
|
||||
function pinToString(pinNo, portSize) {
|
||||
let useLetters = true
|
||||
if (portSize > 1000) {
|
||||
portSize = portSize % 1000;
|
||||
useLetters = true
|
||||
}
|
||||
let port = (pinNo / portSize) | 0
|
||||
let pin = pinNo % portSize
|
||||
if (useLetters) {
|
||||
return "P" + String.fromCharCode(65 + port) + ("0" + pin.toString()).slice(-2)
|
||||
} else {
|
||||
return "P" + port + "_" + pin
|
||||
}
|
||||
}
|
||||
|
||||
function showKV(k, v, portSize) {
|
||||
let vn = ""
|
||||
|
||||
let kn = configInvKeys[k + ""] || ""
|
||||
|
||||
if (enums[kn]) {
|
||||
for (let en of Object.keys(enums[kn])) {
|
||||
if (enums[kn][en] == v) {
|
||||
vn = en
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vn == "") {
|
||||
if (/_CFG/.test(kn) || v > 10000)
|
||||
vn = "0x" + v.toString(16)
|
||||
else if (/^PIN_/.test(kn))
|
||||
vn = pinToString(v, portSize)
|
||||
else
|
||||
vn = v + ""
|
||||
}
|
||||
|
||||
if (kn == "")
|
||||
kn = "_" + k
|
||||
|
||||
return `${kn} = ${vn}`
|
||||
}
|
||||
|
||||
function readConfig(buf) {
|
||||
init()
|
||||
let cfgdata = readWriteConfig(buf, null)
|
||||
let portSize = lookupCfg(cfgdata, configKeys.PINS_PORT_SIZE)
|
||||
//if (portSize === null)
|
||||
// cfgdata.push(configKeys.PINS_PORT_SIZE, portSize = enums.PINS_PORT_SIZE.PA_16)
|
||||
let numentries = cfgdata.length >> 1
|
||||
let lines = []
|
||||
for (let i = 0; i < numentries; ++i) {
|
||||
lines.push(showKV(cfgdata[i * 2], cfgdata[i * 2 + 1], portSize))
|
||||
}
|
||||
lines.sort()
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
function patchConfig(buf, cfg) {
|
||||
init()
|
||||
const cfgMap = {}
|
||||
let lineNo = 0
|
||||
for (let line of cfg.split(/\n/)) {
|
||||
lineNo++
|
||||
line = line.replace(/(#|\/\/).*/, "")
|
||||
line = line.trim()
|
||||
if (!line)
|
||||
continue
|
||||
let m = /(\w+)\s*=\s*(\w+)/.exec(line)
|
||||
if (!m)
|
||||
err("syntax error at config line " + lineNo)
|
||||
let kn = m[1].toUpperCase()
|
||||
let k = configKeys[kn]
|
||||
if (!k && /^_\d+$/.test(kn))
|
||||
k = parseInt(kn.slice(1))
|
||||
if (!k)
|
||||
err("Unrecognized key name: " + kn)
|
||||
cfgMap[configKeys[kn] + ""] = m[2]
|
||||
}
|
||||
|
||||
let cfgdata = readWriteConfig(buf, null)
|
||||
|
||||
for (let i = 0; i < cfgdata.length; i += 2) {
|
||||
let k = cfgdata[i] + ""
|
||||
if (!cfgMap.hasOwnProperty(k))
|
||||
cfgMap[k] = cfgdata[i + 1] + ""
|
||||
}
|
||||
|
||||
const forAll = f => {
|
||||
for (let k of Object.keys(cfgMap)) {
|
||||
let kn = configInvKeys[k]
|
||||
f(kn, k, cfgMap[k])
|
||||
}
|
||||
}
|
||||
|
||||
// expand enums
|
||||
forAll((kn, k, v) => {
|
||||
let e = enums[kn]
|
||||
if (e && e[v.toUpperCase()])
|
||||
cfgMap[k] = e[v] + ""
|
||||
})
|
||||
|
||||
let portSize = cfgMap[configKeys.PINS_PORT_SIZE]
|
||||
if (portSize) portSize = parseInt(portSize)
|
||||
let portSize0 = portSize
|
||||
if (portSize)
|
||||
portSize = portSize % 1000
|
||||
|
||||
// expand pin names
|
||||
forAll((kn, k, v) => {
|
||||
let thePort = -1
|
||||
let pin = -1
|
||||
|
||||
let m = /^P([A-Z])_?(\d+)$/.exec(v)
|
||||
if (m) {
|
||||
pin = parseInt(m[2])
|
||||
thePort = m[1].charCodeAt(0) - 65
|
||||
}
|
||||
|
||||
m = /^P(\d+)_(\d+)$/.exec(v)
|
||||
if (m) {
|
||||
pin = parseInt(m[2])
|
||||
thePort = parseInt(m[1])
|
||||
}
|
||||
|
||||
if (thePort >= 0) {
|
||||
if (!portSize) err("PINS_PORT_SIZE not specified, while trying to parse PIN " + v)
|
||||
if (pin >= portSize) err("Pin name invalid: " + v)
|
||||
cfgMap[k] = (thePort * portSize + pin) + ""
|
||||
}
|
||||
})
|
||||
|
||||
// expand existing keys
|
||||
for (let i = 0; i < 10; ++i)
|
||||
forAll((kn, k, v) => {
|
||||
if (configKeys[v]) {
|
||||
let curr = cfgMap[configKeys[v] + ""]
|
||||
if (curr == null)
|
||||
err("Value not specified, but referenced: " + v)
|
||||
cfgMap[k] = curr
|
||||
}
|
||||
})
|
||||
|
||||
forAll((kn, k, v) => {
|
||||
if (isNaN(parseInt(v)))
|
||||
err("Value not understood: " + v)
|
||||
})
|
||||
|
||||
let sorted = Object.keys(cfgMap)
|
||||
sorted.sort((a, b) => parseInt(a) - parseInt(b))
|
||||
let patch = []
|
||||
for (let k of sorted) {
|
||||
patch.push(parseInt(k))
|
||||
patch.push(parseInt(cfgMap[k]))
|
||||
}
|
||||
|
||||
let changes = ""
|
||||
for (let i = 0; i < patch.length; i += 2) {
|
||||
let k = patch[i]
|
||||
let v = patch[i + 1]
|
||||
let old = lookupCfg(cfgdata, k)
|
||||
if (old != v) {
|
||||
let newOne = showKV(k, v, portSize0)
|
||||
if (old !== null) {
|
||||
let oldOne = showKV(k, old, portSize0)
|
||||
newOne += " (was: " + oldOne.replace(/.* = /, "") + ")"
|
||||
}
|
||||
changes += newOne + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
readWriteConfig(buf, patch)
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
function parseHFile(hFile) {
|
||||
if (!hFile) return
|
||||
for (let line of hFile.split(/\n/)) {
|
||||
line = line.trim()
|
||||
let m = /#define\s+CFG_(\w+)\s+(\d+)/.exec(line)
|
||||
if (m) {
|
||||
let k = m[1]
|
||||
let v = parseInt(m[2])
|
||||
configKeys[k] = parseInt(v)
|
||||
configInvKeys[v + ""] = k
|
||||
console.log(` ${k}: ${v},`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
for (let k of Object.keys(configKeys)) {
|
||||
let v = configKeys[k]
|
||||
configInvKeys[v + ""] = k
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
let uf2 = readBin(process.argv[2])
|
||||
|
||||
if (process.argv[3]) {
|
||||
let cfg = readBin(process.argv[3]).toString("utf8")
|
||||
let changes = patchConfig(uf2, cfg)
|
||||
if (!changes)
|
||||
console.log("No changes.")
|
||||
else
|
||||
console.log("\nChanges:\n" + changes)
|
||||
console.log("# Writing config...")
|
||||
fs.writeFileSync(process.argv[2], uf2)
|
||||
} else {
|
||||
console.log(readConfig(uf2))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (typeof window == "undefined")
|
||||
main()
|
||||
94
patcher/web.js
Normal file
94
patcher/web.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
"use strict";
|
||||
|
||||
function savePatch(ev) {
|
||||
let text = document.getElementById("patch")
|
||||
localStorage["UF2_PATCH"] = text.value
|
||||
}
|
||||
|
||||
function restorePatch() {
|
||||
let text = document.getElementById("patch")
|
||||
text.value = localStorage["UF2_PATCH"]
|
||||
document.getElementById("apply").onclick = applyPatch
|
||||
}
|
||||
|
||||
let currUF2 = null
|
||||
let currUF2Name = ""
|
||||
|
||||
function showMSG() {
|
||||
if (infoMsg)
|
||||
document.getElementById("currconfig").textContent = infoMsg
|
||||
}
|
||||
|
||||
function wrap(f) {
|
||||
try {
|
||||
infoMsg = ""
|
||||
f()
|
||||
showMSG()
|
||||
} catch (e) {
|
||||
log("Exception: " + e.message)
|
||||
showMSG()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function applyPatch() {
|
||||
wrap(() => {
|
||||
let text = document.getElementById("patch")
|
||||
let newcfg = text.value.trim()
|
||||
if (!currUF2)
|
||||
log("You have to drop a UF2 file with bootloader above before applying patches.")
|
||||
else if (!newcfg)
|
||||
log("You didn't give any patch to apply.")
|
||||
else {
|
||||
let buf = currUF2.slice()
|
||||
let changes = patchConfig(buf, newcfg)
|
||||
if (!changes) {
|
||||
log("No changes.")
|
||||
} else {
|
||||
log("\nChanges:\n" + changes)
|
||||
log("Downloading " + currUF2Name)
|
||||
|
||||
let blob = new Blob([buf], {
|
||||
type: "application/x-uf2"
|
||||
});
|
||||
let url = URL.createObjectURL(blob);
|
||||
|
||||
let a = document.createElement("a");
|
||||
document.body.appendChild(a);
|
||||
a.style = "display: none";
|
||||
a.href = url;
|
||||
a.download = currUF2Name
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function dropHandler(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
for (let i = 0; i < ev.dataTransfer.items.length; i++) {
|
||||
if (ev.dataTransfer.items[i].kind === 'file') {
|
||||
let file = ev.dataTransfer.items[i].getAsFile();
|
||||
let reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
wrap(() => {
|
||||
let buf = new Uint8Array(reader.result)
|
||||
let cfg = readConfig(buf)
|
||||
currUF2 = buf
|
||||
infoMsg = ""
|
||||
currUF2Name = file.name
|
||||
document.getElementById("currconfig").textContent = cfg
|
||||
})
|
||||
}
|
||||
reader.readAsArrayBuffer(file);
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dragOverHandler(ev) {
|
||||
ev.preventDefault();
|
||||
ev.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
Loading…
Reference in a new issue