@microbit/microbit-fs
Version:
Manipulate files in a micro:bit MicroPython Intel Hex file.
133 lines • 5.58 kB
JavaScript
/**
* Module to add and remove Python scripts into and from a MicroPython hex.
*
* (c) 2019 Micro:bit Educational Foundation and the microbit-fs contributors.
* SPDX-License-Identifier: MIT
*/
import MemoryMap from 'nrf-intel-hex';
import { bytesToStr, strToBytes } from './common.js';
/** User script located at specific flash address. */
var AppendedBlock;
(function (AppendedBlock) {
AppendedBlock[AppendedBlock["StartAdd"] = 253952] = "StartAdd";
AppendedBlock[AppendedBlock["Length"] = 8192] = "Length";
AppendedBlock[AppendedBlock["EndAdd"] = 262144] = "EndAdd";
})(AppendedBlock || (AppendedBlock = {}));
/** User code header */
var AppendedHeader;
(function (AppendedHeader) {
AppendedHeader[AppendedHeader["Byte0"] = 0] = "Byte0";
AppendedHeader[AppendedHeader["Byte1"] = 1] = "Byte1";
AppendedHeader[AppendedHeader["CodeLengthLsb"] = 2] = "CodeLengthLsb";
AppendedHeader[AppendedHeader["CodeLengthMsb"] = 3] = "CodeLengthMsb";
AppendedHeader[AppendedHeader["Length"] = 4] = "Length";
})(AppendedHeader || (AppendedHeader = {}));
/** Start of user script marked by "MP" + 2 bytes for the script length. */
const HEADER_START_BYTE_0 = 77; // 'M'
const HEADER_START_BYTE_1 = 80; // 'P'
/** How many bytes per Intel Hex record line. */
const HEX_RECORD_DATA_LEN = 16;
/**
* Marker placed inside the MicroPython hex string to indicate where to
* inject the user Python Code.
*/
const HEX_INSERTION_POINT = ':::::::::::::::::::::::::::::::::::::::::::\n';
/**
* Removes the old insertion line the input Intel Hex string contains it.
*
* @param intelHex - String with the intel hex lines.
* @returns The Intel Hex string without insertion line.
*/
export function cleanseOldHexFormat(intelHex) {
return intelHex.replace(HEX_INSERTION_POINT, '');
}
/**
* Parses through an Intel Hex string to find the Python code at the
* allocated address and extracts it.
*
* @param intelHex - Intel Hex block to scan for the code.
* @return Python code.
*/
function getIntelHexAppendedScript(intelHex) {
let pyCode = '';
const hexFileMemMap = MemoryMap.fromHex(intelHex);
// Check that the known flash location has user code
if (hexFileMemMap.has(AppendedBlock.StartAdd)) {
const pyCodeMemMap = hexFileMemMap.slice(AppendedBlock.StartAdd, AppendedBlock.Length);
const codeBytes = pyCodeMemMap.get(AppendedBlock.StartAdd);
if (codeBytes[AppendedHeader.Byte0] === HEADER_START_BYTE_0 &&
codeBytes[AppendedHeader.Byte1] === HEADER_START_BYTE_1) {
pyCode = bytesToStr(codeBytes.slice(AppendedHeader.Length));
// Clean null terminators at the end
pyCode = pyCode.replace(/\0/g, '');
}
}
return pyCode;
}
/**
* When the user code is inserted into the flash known location it needs to be
* packed with a header. This function outputs a byte array with a fully formed
* User Code Block.
*
* @param dataBytes - Array of bytes to include in the User Code block.
* @returns Byte array with the full User Code Block.
*/
function createAppendedBlock(dataBytes) {
let blockLength = dataBytes.length + AppendedHeader.Length;
// Old DAPLink versions need padding on the last record to fill the line
if (blockLength % HEX_RECORD_DATA_LEN) {
blockLength += HEX_RECORD_DATA_LEN - (blockLength % HEX_RECORD_DATA_LEN);
}
const blockBytes = new Uint8Array(blockLength).fill(0x00);
// The user script block has to start with "MP" marker + script length
blockBytes[0] = HEADER_START_BYTE_0;
blockBytes[1] = HEADER_START_BYTE_1;
blockBytes[2] = dataBytes.length & 0xff;
blockBytes[3] = (dataBytes.length >> 8) & 0xff;
blockBytes.set(dataBytes, AppendedHeader.Length);
return blockBytes;
}
/**
* Converts the Python code into the Intel Hex format expected by
* MicroPython and injects it into a Intel Hex string containing a marker.
*
* TODO: Throw error if filesystem is using the penultimate page already.
*
* @param intelHex - Single string of Intel Hex records to inject the code.
* @param pyStr - Python code string.
* @returns Intel Hex string with the Python code injected.
*/
function addIntelHexAppendedScript(intelHex, pyCode) {
const codeBytes = strToBytes(pyCode);
const blockBytes = createAppendedBlock(codeBytes);
if (blockBytes.length > AppendedBlock.Length) {
throw new RangeError('Too long');
}
// Convert to Intel Hex format
const intelHexClean = cleanseOldHexFormat(intelHex);
const intelHexMap = MemoryMap.fromHex(intelHexClean);
intelHexMap.set(AppendedBlock.StartAdd, blockBytes);
// Older versions of DAPLink need the file to end in a new line
return intelHexMap.asHexString() + '\n';
}
/**
* Checks the Intel Hex memory map to see if there is an appended script.
*
* @param intelHexMap - Memory map for the MicroPython Intel Hex.
* @returns True if appended script is present, false otherwise.
*/
function isAppendedScriptPresent(intelHex) {
let intelHexMap;
if (typeof intelHex === 'string') {
const intelHexClean = cleanseOldHexFormat(intelHex);
intelHexMap = MemoryMap.fromHex(intelHexClean);
}
else {
intelHexMap = intelHex;
}
const headerMagic = intelHexMap.slicePad(AppendedBlock.StartAdd, 2, 0xff);
return (headerMagic[0] === HEADER_START_BYTE_0 &&
headerMagic[1] === HEADER_START_BYTE_1);
}
export { AppendedBlock, addIntelHexAppendedScript, getIntelHexAppendedScript, isAppendedScriptPresent, };
//# sourceMappingURL=micropython-appended.js.map