www/m/js/modules/hexdump/Hexdump.js
import {HexPairNavigator} from './HexPairNavigator';
import {NavigatorDirection} from '../../core/NavigatorDirection';
import {RadareInfiniteBlock} from '../../layout/RadareInfiniteBlock';
import {FlexContainer} from '../../layout/FlexContainer';
import {WordSizes} from './WordSizes';
import {uiContext} from '../../core/UIContext';
import {Widgets} from '../../widgets/Widgets';
import {Inputs} from '../../helpers/Inputs';
import {applySeek, formatOffset} from '../../helpers/Format';
import {r2Wrapper} from '../../core/R2Wrapper';
/**
* UI management
* Container should be currently sized for the purpose
* lineHeight should be specified in pixels
*/
export class Hexdump extends RadareInfiniteBlock {
constructor(containerElement, lineHeight, isBigEndian) {
super();
this.container = new FlexContainer(containerElement, 'hex');
this.lineHeight = lineHeight;
this.bigEndian = isBigEndian;
this.nbColumns = 16;
this.hexLength = WordSizes.PAIRS;
this.init();
this.resetContainer(containerElement);
this.showFlags = true;
this.beingSelected = false;
this.selectionFirst;
this.selectionEnd;
this.lastColorUsed = -1;
this.bgColors = [
'rgba(255,0,0,0.2)',
'rgba(0,255,0,0.2)',
'rgba(0,92,192,0.2)',
'rgba(255,255,0,0.2)',
'rgba(255,0,255,0.2)',
'rgba(0,255,255,0.2)'
];
this.flagColorAssociation = [];
}
/**
* Define the behavior expected when a value is edited
*/
setOnChangeCallback(callback) {
this.onChangeCallback = callback;
}
/**
* Fetch and initialize data
*/
init() {
this.refreshInitialOffset();
r2.cmdj('ecj|', (colors) => { this.colors = colors; });
r2.cmdj('ij|', (info) => { this.writable = info.core.iorw; });
this.selectionMode = !this.writable;
for (var key in this.colors) {
this.colors[key] = 'rgb(' + this.colors[key][0] + ',' + this.colors[key][1] + ',' + this.colors[key][2] + ')';;
}
window.addEventListener('mousedown', (evt) => {
if (evt.button !== 0 || this.contextMenuOpen) {
return;
}
this.cleanSelection();
});
this.drawContextualMenu();
this.changeWritable();
}
/**
* Generic definition of isWritable, if not, we are in select mode
*/
isWritable() {
return this.writable && !this.selectionMode;
}
/**
* On change on R/W status on document (!= this.isWritable)
*/
changeWritable() {
var items = Array.prototype.slice.call(document.getElementsByClassName('writableMenu'));
var opacity = (this.writable) ? 1.0 : 0.5;
for (var i = 0 ; i < items.length ; i++) {
items[i].style.opacity = opacity;
}
}
/**
* Called when the frame need to be redrawn
* Reset the container and draw the previous state
* TODO: save DOM/Events when quitting widget to reload it faster
*/
resetContainer(container) {
this.refreshInitialOffset();
if (typeof this.nav !== 'undefined') {
this.nav.reset();
}
this.container.replug(container);
// TODO: cache, faster
this.container.reset();
this.container.drawBody((element) => {
element.appendChild(document.createElement('div')); // offsets
element.appendChild(document.createElement('div')); // hexpairs
element.appendChild(document.createElement('div')); // ascii
});
this.content = this.container.getBody();
this.defineInfiniteParams();
}
getCurrentSelection() {
return this.currentSelection;
}
/**
* Gather data and set event to configure infinite scrolling
*/
defineInfiniteParams() {
RadareInfiniteBlock.prototype.defineInfiniteParams.call(this);
this.nav = new HexPairNavigator(this.howManyLines, this.nbColumns, this.initialOffset);
this.nav.updateModifications();
}
/**
* Sequence to draw the whole UI
*/
draw() {
this.drawControls(this.container.getControls());
this.drawContent(this.container.getBody(), () => {
this.colorizeFlag();
});
}
/**
* Colorize a byte depending on 00/7f/ff and ASCII
*/
colorizeByte(elem, val) {
if (val === '00' || val === 'ff' || val === '7f') {
elem.style.color = this.colors['b0x' + val];
} else if (isAsciiVisible(parseInt(val, 16))) {
elem.style.color = 'rgb(192,192,192)';
} else {
elem.style.color = 'inherit';
}
}
/**
* Return a color on a cyclic way
*/
pickColor() {
return 'inherit'; // no random anoying colors
this.lastColorUsed = (this.lastColorUsed + 1) % this.bgColors.length;
return this.bgColors[this.lastColorUsed];
}
/** Assemble two pairs depending of endianness */
honoringEndian(x, y) {
if (this.bigEndian) {
return x + y
} else {
return y + x;
}
}
/**
* Convert a pair to a word considering endian
*/
pairs2words(list, wordLength) {
if (wordLength === 1) {
return list;
}
let newList = [];
for (let i = 0 ; i < list.length / 2 ; i++) {
newList.push(
this.honoringEndian(
list[i * 2],
list[(i * 2) + 1]
)
);
}
return this.pairs2words(newList, wordLength / 2);
}
/**
* Delete selection marks from the UI
*/
cleanSelection(previsualization) {
if (typeof previsualization === 'undefined') {
previsualization = false;
}
if (!previsualization) {
this.currentSelection = {};
}
var elems;
do {
elems = this.listContent.getElementsByClassName('selected');
for (var i = 0 ; i < elems.length ; i++) {
elems[i].classList.remove('selected');
}
} while (elems.length > 0);
}
/**
* Draw the selection (emulated)
* Based on sibling
*/
processSelection(isPrev) {
if (isPrev) {
this.cleanSelection(true);
}
if (this.selectionFirst === this.selectionEnd) {
this.selectionFirst.classList.add('selected');
this.currentSelection = {
from: this.selectionFirst.offset,
to: this.selectionFirst.offset
};
}
var start = (this.selectionFirst.offset < this.selectionEnd.offset) ? this.selectionFirst : this.selectionEnd;
var end = (this.selectionFirst.offset < this.selectionEnd.offset) ? this.selectionEnd : this.selectionFirst;
this.currentSelection = {
from: start.offset,
to: end.offset
};
var curNode = start;
var endFound = false;
while (!endFound) {
var sibling = curNode;
curNode.classList.add('selected');
while (sibling !== null) {
if (sibling.offset === end.offset) {
sibling.classList.add('selected');
curNode = sibling;
endFound = true;
return;
}
do {
curNode = sibling;
sibling = sibling.nextSibling;
} while (typeof curNode.offset === 'undefined');
curNode.classList.add('selected');
}
var nextLine = curNode.parentNode.parentNode.nextSibling;
if (nextLine === null) {
return;
}
while (nextLine.children.length <= 1) {
if (nextLine === null) {
return;
}
nextLine = nextLine.nextSibling;
}
curNode = nextLine.children[1].children[0];
}
}
//#region main draw
/**
* Draw 3 chunks on specified DOM node
*/
drawContent(dom, callback) {
dom.innerHTML = '';
this.listContent = document.createElement('ul');
this.listContent.className = 'listContent';
dom.appendChild(this.listContent);
this.listContent.addEventListener('contextmenu', (evt) => {
if (typeof this.currentSelection === 'undefined' ||
typeof this.currentSelection.from === 'undefined' ||
typeof this.currentSelection.to === 'undefined') {
// If undefined, we chose to have one-byte selection
this.currentSelection = {
from: evt.target.offset,
to: evt.target.offset
};
}
evt.preventDefault();
var menu = document.getElementById('contextmenuHex');
if (this.contextMenuOpen) {
menu.classList.remove('active');
} else {
menu.classList.add('active');
menu.style.left = evt.clientX + 'px';
menu.style.top = evt.clientY + 'px';
}
this.contextMenuOpen = !this.contextMenuOpen;
});
this.nav.get(NavigatorDirection.CURRENT, (chunk) => {
this.curChunk = chunk;
});
this.nav.get(NavigatorDirection.BEFORE, (chunk) => {
this.isTopMax = chunk.offset === 0;
this.drawChunk(chunk);
this.firstElement = this.drawChunk(this.getCurChunk());
});
this.nav.get(NavigatorDirection.AFTER, (chunk) => {
this.drawChunk(chunk);
this.content.scrollTop = 0;
this.content.scrollTop = this.getFirstElement().getBoundingClientRect().top;
// Everything has been drawn, maybe we should do something more
if (typeof callback !== 'undefined') {
callback();
}
});
}
/**
* Draw a chunk before or after the current content
*/
drawChunk(chunk, where) {
if (chunk.offset === 0 && chunk.hex.length === 0) {
return this.firstElement;
}
var drawMethod;
var size;
if (this.hexLength === -1) {
drawMethod = this.drawPairs_;
} else {
drawMethod = this.drawWords_;
size = this.hexLength;
}
if (typeof where === 'undefined') {
where = NavigatorDirection.AFTER;
}
var lines = [];
var firstElement;
var i;
for (var x = 0 ; x < chunk.hex.length ; x++) {
const line = document.createElement('li');
line.className = 'block' + chunk.offset;
if (where === NavigatorDirection.AFTER) {
this.listContent.appendChild(line);
lines.push(line);
i = x;
} else {
this.listContent.insertBefore(line, this.listContent.firstChild);
lines.unshift(line);
i = (chunk.hex.length - 1) - x;
}
line.addEventListener('mousedown', (evt) => { this.currentLine = line; });
line.offset = {};
line.offset.start = chunk.offset + (this.nbColumns * i);
line.offset.end = line.offset.start + (this.nbColumns - 1);
var offset = document.createElement('ul');
var hexpairs = document.createElement('ul');
var asciis = document.createElement('ul');
offset.classList.add('offset');
var offsetEl = document.createElement('li');
offset.appendChild(offsetEl);
const hex = int2fixedHex(chunk.offset + (i * this.nbColumns), 8);
const offsetElA = document.createElement('a');
offsetElA.innerHTML = hex;
applySeek(offsetElA);
offsetEl.appendChild(offsetElA);
offsetEl.assoc = hexpairs;
hexpairs.classList.add('hexpairs');
asciis.classList.add('ascii');
line.appendChild(offset);
line.appendChild(hexpairs);
line.appendChild(asciis);
drawMethod.apply(
this,
[hexpairs, asciis, chunk.hex[i], chunk.ascii[i], chunk.modified, chunk.offset + (this.nbColumns * i), size]
);
if (typeof firstElement === 'undefined') {
firstElement = line;
}
}
this.applyFlags(lines, chunk.offset, chunk.flags);
return firstElement;
}
/**
* Trigerred by scrolling, determine and add content at the right place
*/
infiniteDrawingContent(where, pos, endCallback) {
this.nav.get(where, (chunk) => {
if (where === NavigatorDirection.BEFORE) {
this.isTopMax = chunk.offset === 0;
} else {
if (this.isTopMax) {
this.nav.get(NavigatorDirection.BEFORE, (chunk) => {
if (chunk.offset > 0) {
this.isTopMax = false;
}
});
}
}
if (chunk.offset === 0 && chunk.hex.length === 0) {
return;
}
var removing;
if (where === NavigatorDirection.BEFORE) {
removing = this.listContent.lastChild.className;
} else {
removing = this.listContent.firstChild.className;
}
var elements = Array.prototype.slice.call(document.getElementsByClassName(removing));
for (var i = 0 ; i < elements.length ; i++) {
elements[i].parentNode.removeChild(elements[i]);
}
this.drawChunk(chunk, where);
this.content.scrollTop = pos;
this.colorizeFlag(true);
endCallback(this.isTopMax); // pauseScrollEvent = false
});
}
/**
* mouse over event to highligh pair-ascii at the same time
*/
showPairs_(first, second, isOver) {
if (isOver) {
first.classList.add('active');
second.classList.add('active');
} else {
first.classList.remove('active');
second.classList.remove('active');
}
}
/**
* Generic method to draw words of any size
*/
drawWords_(hexpairs, asciis, pairs, chars, modifications, offset, size) {
var words = this.pairs2words(pairs, size);
hexpairs.classList.add('words');
for (var x = 0 ; x < pairs.length ; x++) {
var asciiEl = document.createElement('li');
asciiEl.appendChild(document.createTextNode(chars[x]));
asciis.appendChild(asciiEl);
this.colorizeByte(asciiEl, pairs[x]);
}
for (var x = 0 ; x < words.length ; x++) {
var hexpairEl = document.createElement('li');
var contentNode;
if (size === 2) {
var word = '' + new Int16Array([+words[x]])[0]
if (word.length < 5) {
word = Array(5 - word.length).join('_') + word
}
contentNode = document.createTextNode(word);
} else {
contentNode = document.createElement('a');
contentNode.innerHTML = '0x' + words[x];
applySeek(contentNode);
}
hexpairEl.appendChild(contentNode);
hexpairs.appendChild(hexpairEl);
}
}
/**
* Default drawing method to draw the pairs with all features
*/
drawPairs_(hexpairs, asciis, pairs, chars, modifications, offset) {
hexpairs.classList.add('pairs');
var editableHexEvent = {
keydown: (evt) => evt.keyCode === 13 && collectHexpair(evt.target),
blur: (evt) => collectHexpair(evt.target)
};
var editableAsciiEvent = {
keydown: (evt) => evt.keyCode === 13 && collectAscii(evt.target),
blur: (evt) => collectAscii(evt.target)
};
var collectHexpair = (target) => {
if (target.busy) {
return; // Event has been already triggered elsewhere
}
// Don't need to set to false, in each case we remove the node
target.busy = true;
// Keep the first 2 valid hex characters
var regex = target.value.match(/$([a-fA-F0-9]{2})^/);
if (regex === null) {
if (typeof target.parentNode === 'undefined') {
// Solving event conflict
return;
}
alert('Wrong format, expected: [a-fA-F0-9]{2}');
target.parentNode.innerHTML = target.initValue;
return;
}
var value = regex[0];
target = target.parentNode;
var initial = this.nav.reportChange(target.offset, value);
target.innerHTML = value;
target.assoc.innerHTML = hexPairToASCII(value);
if (initial !== null) {
target.classList.add('modified');
target.assoc.classList.add('modified');
this.colorizeByte(target, value);
this.colorizeByte(target.assoc, value);
this.onChangeCallback(target.offset, initial, value);
}
target.removeEventListener('keydown', editableHexEvent.keydown);
target.removeEventListener('blur', editableHexEvent.blur);
};
var collectAscii = (target) => {
var value = target.value[0];
var hex = ASCIIToHexpair(value);
target = target.parentNode;
var initial = this.nav.reportChange(target.assoc.offset, hex);
target.innerHTML = value;
target.assoc.innerHTML = hex;
if (initial !== null) {
target.classList.add('modified');
target.assoc.classList.add('modified');
this.colorizeByte(target, value);
this.colorizeByte(target.assoc, value);
this.onChangeCallback(target.assoc.offset, target.assoc.innerHTML, hex);
}
target.removeEventListener('keydown', editableAsciiEvent.keydown);
target.removeEventListener('blur', editableAsciiEvent.blur);
};
for (var x = 0 ; x < pairs.length ; x++) {
var curOffset = offset + x;
// If there is a one-byte modification (UI not refresh)
var checkModification = this.nav.hasNewValue(curOffset);
// If there is a modification known by r2
var isModified = this.nav.isModifiedByte(curOffset);
// If it's a small modification, we update content
if (checkModification !== null) {
pairs[x] = checkModification;
chars[x] = hexPairToASCII(checkModification);
isModified = true;
}
var hexpairEl = document.createElement('li');
hexpairEl.appendChild(document.createTextNode(pairs[x]));
hexpairEl.offset = curOffset;
if (isModified) {
hexpairEl.classList.add('modified');
}
var asciiEl = document.createElement('li');
asciiEl.appendChild(document.createTextNode(chars[x]));
if (isModified) {
asciiEl.classList.add('modified');
}
asciiEl.assoc = hexpairEl;
hexpairEl.assoc = asciiEl;
hexpairs.appendChild(hexpairEl);
asciis.appendChild(asciiEl);
this.colorizeByte(hexpairEl, pairs[x]);
this.colorizeByte(asciiEl, pairs[x]);
hexpairEl.addEventListener('mouseenter', (evt) => this.showPairs_(evt.target, evt.target.assoc, true));
hexpairEl.addEventListener('mouseleave', (evt) => this.showPairs_(evt.target, evt.target.assoc, false));
asciiEl.addEventListener('mouseenter', (evt) => this.showPairs_(evt.target, evt.target.assoc, true));
asciiEl.addEventListener('mouseleave', (evt) => this.showPairs_(evt.target, evt.target.assoc, false));
if (this.isWritable()) {
hexpairEl.addEventListener('click', (evt) => {
if (evt.button !== 0) {
return;
}
evt.preventDefault();
var form = document.createElement('input');
form.maxLength = 2;
form.initValue = evt.target.innerHTML;
form.value = evt.target.innerHTML;
form.pattern = '[a-fA-F0-9]{2}';
evt.target.innerHTML = '';
evt.target.appendChild(form);
form.busy = false; // Race-flag
form.addEventListener('keydown', editableHexEvent.keydown);
form.addEventListener('blur', editableHexEvent.blur);
form.focus();
});
asciiEl.addEventListener('click', (evt) => {
if (evt.button !== 0) {
return;
}
evt.preventDefault();
var form = document.createElement('input');
form.maxLength = 1;
form.value = evt.target.innerHTML;
form.pattern = '(.){1}';
evt.target.innerHTML = '';
evt.target.appendChild(form);
form.addEventListener('keydown', editableAsciiEvent.keydown);
form.addEventListener('blur', editableAsciiEvent.blur);
form.focus();
});
} else {
hexpairEl.addEventListener('click', () => {
this.beingSelected = false;
this.cleanSelection();
});
const stopSelection = () => {
this.beingSelected = false;
window.removeEventListener('mouseup', stopSelection);
};
hexpairEl.addEventListener('mousedown', (evt) => {
if (evt.button !== 0) {
return;
}
evt.preventDefault();
this.beingSelected = true;
this.selectionFirst = evt.target;
window.addEventListener('mouseup', stopSelection);
});
hexpairEl.addEventListener('mouseover', (evt) => {
if (!this.beingSelected) {
return;
}
this.selectionEnd = evt.target;
this.processSelection(true);
});
hexpairEl.addEventListener('mouseup', (evt) => {
if (!this.beingSelected) {
return;
}
this.selectionEnd = evt.target;
this.processSelection(false);
});
}
}
}
//#endregion
//#region aux draw
/**
* Populate the content of the contextual menu (on hexpair selection)
*/
drawContextualMenu() {
var exportOp = (name, range, command, ext) => {
var output;
r2.cmd(command + ' ' + (range.to - range.from) + ' @' + range.from, (d) => { output = d; });
var dialog = this.createExportDialog('Export as ' + name + ':', output, () => {
var blob = new Blob([output], {type: 'text/plain'});
var fileName;
r2.cmdj('ij', (d) => { fileName = basename(d.core.file); });
fileName += '_0x' + range.from.toString(16) + '-0x' + range.to.toString(16) + '.' + ext;
saveAs(blob, fileName);
});
document.body.appendChild(dialog);
componentHandler.upgradeDom();
dialog.showModal();
};
var bytes;
var exportAs = [
{ name: 'Assembly', fct: (evt, range) => exportOp('ASM', range, 'pca', 'asm') },
{ name: 'Disassembly', fct: (evt, range) => exportOp('DISASM', range, 'pD', 'disasm') },
{ name: 'Hexpairs', fct: (evt, range) => exportOp('HEXPAIRS', range, 'p8', 'disasm') },
{ name: 'Base64 Encode', fct: (evt, range) => exportOp('b64e', range, 'p6e', 'disasm') },
{ name: 'Base64 Decode', fct: (evt, range) => exportOp('b64d', range, 'p6d', 'disasm') },
{ name: 'Binary', fct: (evt, range) => { bytes = new Uint8Array(this.nav.getBytes(range));
var blob = new Blob([bytes], {type: 'application/octet-stream'});
var fileName;
r2.cmdj('ij', (d) => { fileName = basename(d.core.file); });
fileName += '_0x' + range.from.toString(16) + '-0x' + range.to.toString(16) + '.bin';
saveAs(blob, fileName);
} },
{ name: 'C', fct: (evt, range) => exportOp('C', range, 'pc', 'c') },
{ name: 'C half-words (2 bytes)', fct: (evt, range) => exportOp('C', range, 'pch', 'c') },
{ name: 'C words (4 bytes)', fct: (evt, range) => exportOp('C', range, 'pcw', 'c') },
{ name: 'C dwords (8 bytes)', fct: (evt, range) => exportOp('C', range, 'pcd', 'c') },
{ name: 'JavaScript', fct: (evt, range) => exportOp('JS', range, 'pcJ', 'js') },
{ name: 'JSON', fct: (evt, range) => exportOp('JSON', range, 'pcj', 'json') },
{ name: 'Python', fct: (evt, range) => exportOp('Python', range, 'pcp', 'py') },
{ name: 'R2 commands', fct: (evt, range) => exportOp('R2 cmd', range, 'pc*', 'r2') },
{ name: 'Shell script', fct: (evt, range) => exportOp('Shell script', range, 'pcS', 'txt') },
{ name: 'String', fct: (evt, range) => exportOp('string', range, 'pcs', 'txt') }
];
var applyOp = (range, operande) => {
var val = prompt('Value (valid hexpair):');
var op = operande + ' ' + val + ' ' + (range.to - range.from) + ' @' + range.from;
r2.cmd(op, () => console.log('Call: ' + op));
this.nav.updateModifications();
// Send modifications and reload
this.nav.refreshCurrent(() => this.draw());
};
var operations = [
{ name: 'addition', fct: (evt, range) => applyOp(range, 'woa') },
{ name: 'and', fct: (evt, range) => applyOp(range, 'woA') },
{ name: 'divide', fct: (evt, range) => applyOp(range, 'wod') },
{ name: 'shift left', fct: (evt, range) => applyOp(range, 'wol') },
{ name: 'multiply', fct: (evt, range) => applyOp(range, 'wom') },
{ name: 'or', fct: (evt, range) => applyOp(range, 'woo') },
{ name: 'shift right', fct: (evt, range) => applyOp(range, 'wor') },
{ name: 'substraction', fct: (evt, range) => applyOp(range, 'wos') },
{ name: 'write looped', fct: (evt, range) => applyOp(range, 'wow') },
{ name: 'xor', fct: (evt, range) => applyOp(range, 'wox') },
{ name: '2 byte endian swap', fct: (evt, range) => applyOp(range, 'wo2') },
{ name: '4 byte endian swap', fct: (evt, range) => applyOp(range, 'wo4') }
];
var items = [
/*
TODO
{
name: 'Copy length @offset to cmd-line',
fct: function(evt, range) {
console.log('Not implemented');
}
},
{
name: 'Copy bytes to cmd-line',
fct: function(evt, range) {
console.log('Not implemented');
}
},*/
{
name: 'Select line',
fct: (evt, range) => {
this.selectionFirst = this.currentLine.children[1].children[0];
this.selectionEnd = this.currentLine.children[1].children[this.currentLine.children[1].children.length - 1];
this.processSelection(true);
}
},
{
name: 'Set flag',
fct: (evt, range) => {
var name = prompt('Flag\'s name:');
r2.cmd('f ' + name + ' ' + (range.to - range.from + 1) + ' @' + range.from, () => {
this.nav.refreshCurrent(() => this.draw());
});
}
},
{
name: 'Export as...',
expand: exportAs,
requireWritable: false
},
{
name: 'Operations...',
expand: operations,
requireWritable: true
}
];
var menu = document.createElement('nav');
menu.id = 'contextmenuHex';
menu.classList.add('context-menu');
var ul = document.createElement('ul');
menu.appendChild(ul);
// var bindAction = function(element, action) {
// element.addEventListener('mousedown', (function(fct) {
// return function(evt) {
// fct(evt, _this.getCurrentSelection());
// };
// }(action)));
// };
var bindAction = (element, action) => {
element.addEventListener('mousedown', (evt) => {
action(evt, this.getCurrentSelection());
})
};
for (var i = 0 ; i < items.length ; i++) {
var li = document.createElement('li');
ul.appendChild(li);
li.appendChild(document.createTextNode(items[i].name));
li.isSubOpen = false;
li.requireWritable = items[i].requireWritable;
if (items[i].requireWritable) {
li.classList.add('writableMenu');
}
li.addEventListener('mouseenter', (evt) => {
// Cleaning old "active"
var subactives = Array.prototype.slice.call(evt.target.parentNode.getElementsByClassName('subactive'));
for (var x = 0 ; x < subactives.length ; x++) {
subactives[x].classList.remove('subactive');
subactives[x].isSubOpen = false;
}
});
// expandable menu
if (typeof items[i].expand !== 'undefined') {
// Make submenu reachable
li.addEventListener('mouseenter', (evt) => {
// If not available on read-only mode
if (evt.target.requireWritable && !this.writable) {
return;
}
if (evt.target.isSubOpen) {
return;
} else {
evt.target.isSubOpen = true;
}
var subMenu = evt.target.children[0];
if (typeof subMenu === 'undefined') {
return;
}
var dim = evt.target.getBoundingClientRect();
var indexOf = Array.prototype.slice.call(evt.target.parentNode.children).indexOf(evt.target);
evt.target.classList.add('subactive');
subMenu.style.left = dim.width + 'px';
subMenu.style.top = indexOf * dim.height + 'px';
});
// Creating sub menu
var subUl = document.createElement('ul');
li.appendChild(subUl);
for (var j = 0 ; j < items[i].expand.length ; j++) {
var subLi = document.createElement('li');
subUl.appendChild(subLi);
subLi.appendChild(document.createTextNode(items[i].expand[j].name));
bindAction(subLi, items[i].expand[j].fct);
}
} else {
bindAction(li, items[i].fct);
}
}
document.body.appendChild(menu);
componentHandler.upgradeDom();
this.contextMenuOpen = false;
var closeMenu = () => {
if (!this.contextMenuOpen) {
return;
}
menu.classList.remove('active');
this.contextMenuOpen = false;
};
window.onkeyup = (e) => e.keyCode === 27 && closeMenu();
document.addEventListener('click', () => closeMenu());
}
/**
* Return the export dialog built
* Don't forget to normalize the output by calling MDL processing
*/
createExportDialog(label, output, save) {
var dialog = document.createElement('dialog');
dialog.className = 'mdl-dialog';
if (!dialog.showModal) {
dialogPolyfill.registerDialog(dialog);
}
/* CONTENT */
var content = document.createElement('div');
content.className = 'mdl-dialog__content';
dialog.appendChild(content);
var desc = document.createTextNode(label);
content.appendChild(desc);
var textarea = document.createElement('textarea');
textarea.style.width = '100%';
textarea.style.height = '220px';
content.appendChild(textarea);
textarea.value = output;
/* ACTIONS */
var actions = document.createElement('div');
actions.className = 'mdl-dialog__actions';
dialog.appendChild(actions);
var saveButton = document.createElement('button');
saveButton.className = 'mdl-button';
saveButton.innerHTML = 'Save';
saveButton.addEventListener('click', () => {
dialog.close();
dialog.parentNode.removeChild(dialog);
save();
});
actions.appendChild(saveButton);
var closeButton = document.createElement('button');
closeButton.className = 'mdl-button';
closeButton.innerHTML = 'Close';
closeButton.addEventListener('click', () => {
dialog.close();
dialog.parentNode.removeChild(dialog);
});
actions.appendChild(closeButton);
return dialog;
}
/**
* Draw the top-bar controls
*/
drawControls(dom) {
dom.innerHTML = '';
var controlList = document.createElement('ul');
controlList.classList.add('controlList');
dom.appendChild(controlList);
var wordBlock = document.createElement('li');
controlList.appendChild(wordBlock);
var nbColumnsBlock = document.createElement('li');
controlList.appendChild(nbColumnsBlock);
var bigEndianBlock = document.createElement('li');
controlList.appendChild(bigEndianBlock);
var selectionBlock = document.createElement('li');
controlList.appendChild(selectionBlock);
var flagBlock = document.createElement('li');
controlList.appendChild(flagBlock);
var selectWord = document.createElement('span');
/*
selectWord.appendChild(document.createTextNode('Word length: '));
*/
/*
if (0) {
var div = document.createElement('div');
div.className = 'mdl-selectfield mdl-js-selectfield mdl-selectfield--floating-label';
selectWord.appendChild(div);
var select = document.createElement('select');
div.appendChild(select);
select.className = 'mdl-selectfield__select';
selectWord.appendChild(select);
*/
var select = document.createElement('select');
select.className = 'mdl-selectfield mdl-js-selectfield mdl-selectfield--floating-label';
select.style = 'background-color:white;border:1px;color:black;';
selectWord.appendChild(select);
for (var i in WordSizes) {
var option = document.createElement('option');
option.value = WordSizes[i];
option.text = WordSizes[i] > 0 ? (WordSizes[i] * 8) + ' bits' : 'pairs';
if (WordSizes[i] === this.hexLength) {
option.selected = true;
}
select.appendChild(option);
}
select.addEventListener('change', (evt) => {
this.hexLength = parseInt(evt.target.value);
this.draw();
}, false);
// Nb columns
const nbCols = document.createElement('input');
nbCols.className = 'mdl-textfield__input';
nbCols.style.width = '26px';
nbCols.style.display = 'inline';
nbCols.pattern = '[0-9]+';
nbCols.value = this.nbColumns;
var setNbCols = (dom) => {
this.nbColumns = nbCols;
this.nav.changeNbCols(nbCols);
this.draw();
dom.value = nbCols;
};
var selectColumns = document.createElement('span');
selectColumns.title = 'Number of columns per line';
var buttonLess = document.createElement('button');
buttonLess.className = 'mdl-button mdl-js-button mdl-button--icon';
buttonLess.appendChild(document.createTextNode('-'));
buttonLess.addEventListener('click', () => setNbCols(this.nbColumns - 1));
var buttonMore = document.createElement('button');
buttonMore.className = 'mdl-button mdl-js-button mdl-button--icon';
buttonMore.appendChild(document.createTextNode('+'));
buttonMore.addEventListener('click', () => setNbCols(this.nbColumns + 1));
nbCols.addEventListener('change', (evt) => {
var curVal = parseInt(evt.target.value);
setNbCols(curVal);
});
selectColumns.appendChild(buttonLess);
selectColumns.appendChild(document.createTextNode(' '));
selectColumns.appendChild(nbCols);
selectColumns.appendChild(document.createTextNode(' '));
selectColumns.appendChild(buttonMore);
// Big endian
var checkboxBigEndian = document.createElement('input');
checkboxBigEndian.classList.add('mdl-checkbox__input');
checkboxBigEndian.type = 'checkbox';
checkboxBigEndian.checked = this.bigEndian;
var textBigEndian = document.createElement('span');
textBigEndian.classList.add('mdl-checkbox__label');
textBigEndian.appendChild(document.createTextNode('bigEndian'));
var labelCheckboxBE = document.createElement('label');
labelCheckboxBE.classList.add('mdl-checkbox');
labelCheckboxBE.classList.add('mdl-js-checkbox');
labelCheckboxBE.classList.add('mdl-js-ripple-effect');
labelCheckboxBE.appendChild(checkboxBigEndian);
labelCheckboxBE.appendChild(textBigEndian);
checkboxBigEndian.addEventListener('change', () => {
this.bigEndian = !this.bigEndian;
this.draw();
});
// Selection mode
var checboxSelection = document.createElement('input');
checboxSelection.classList.add('mdl-checkbox__input');
checboxSelection.type = 'checkbox';
checboxSelection.checked = this.isWritable();
var textSelection = document.createElement('span');
textSelection.classList.add('mdl-checkbox__label');
textSelection.appendChild(document.createTextNode('isEditable'));
var labelCheckboxSelection = document.createElement('label');
labelCheckboxSelection.classList.add('mdl-checkbox');
labelCheckboxSelection.classList.add('mdl-js-checkbox');
labelCheckboxSelection.classList.add('mdl-js-ripple-effect');
labelCheckboxSelection.appendChild(checboxSelection);
labelCheckboxSelection.appendChild(textSelection);
if (!this.writable) {
checboxSelection.disabled = true;
}
checboxSelection.addEventListener('change', () => {
this.selectionMode = !this.selectionMode;
this.draw();
});
// Big endian
var checkboxFlags = document.createElement('input');
checkboxFlags.classList.add('mdl-checkbox__input');
checkboxFlags.type = 'checkbox';
checkboxFlags.checked = this.showFlags;
var textFlags = document.createElement('span');
textFlags.classList.add('mdl-checkbox__label');
textFlags.appendChild(document.createTextNode('showFlags'));
var labelFlags = document.createElement('label');
labelFlags.classList.add('mdl-checkbox');
labelFlags.classList.add('mdl-js-checkbox');
labelFlags.classList.add('mdl-js-ripple-effect');
labelFlags.appendChild(checkboxFlags);
labelFlags.appendChild(textFlags);
checkboxFlags.addEventListener('change', () => {
this.showFlags = !this.showFlags;
this.draw();
});
wordBlock.appendChild(selectWord);
nbColumnsBlock.appendChild(selectColumns);
bigEndianBlock.appendChild(labelCheckboxBE);
selectionBlock.appendChild(labelCheckboxSelection);
flagBlock.appendChild(labelFlags);
// Call MDL
componentHandler.upgradeDom();
}
//#endregion
//#region flags
/**
* Returns the color associated with the flag
*/
getFlagColor(flagName) {
for (var i = 0 ; i < this.flagColorAssociation.length ; i++) {
if (this.flagColorAssociation[i].name === flagName) {
return this.flagColorAssociation[i].color;
}
}
var color = this.pickColor();
this.flagColorAssociation.push({
name: flagName,
color: color
});
return color;
}
/**
* Draw the flags from the collection of lines (UI POV) currently displayed
*/
applyFlags(lines, blockInitialOffset, flags) {
if (!this.showFlags) {
return;
}
for (var i in flags) {
var line;
var flag = flags[i];
// We select the first line concerned by the flag
for (var j = 0 ; j < lines.length ; j++) {
if (lines[j].offset.start <= flag.offset &&
lines[j].offset.end >= flag.offset) {
line = lines[j];
break;
}
}
// If not found, we pick the next flag
if (typeof line === 'undefined') {
continue;
}
const flagLine = document.createElement('li');
const theOffset = int2fixedHex(flag.offset, 8);
flagLine.classList.add('block' + blockInitialOffset);
flagLine.classList.add('flag');
flagLine.offset = theOffset;
flagLine.appendChild(document.createTextNode('[' + theOffset + '] ' + flag.name));
flagLine.title = 'Go to Disassembly';
flagLine.style.cursor = 'pointer';
flagLine.style.marginTop = '8px'
flagLine.addEventListener('click', () => r2Wrapper.seek(theOffset, Widgets.DISASSEMBLY));
flagLine.title = '(' + flag.size + ' bytes) Seek ' + theOffset + ' on disassembly widget';
flagLine.style.color = 'white'; // this.getFlagColor(flag.name);
this.listContent.insertBefore(flagLine, line);
}
}
/**
* Returns the index of the line who is containing the offset
*/
indexOfLine_(offset) {
var list = [].slice.call(this.listContent.children);
for (var i = 0 ; i < list.length ; i++) {
if (typeof list[i].offset !== 'undefined' &&
list[i].offset.start <= offset &&
list[i].offset.end >= offset) {
return i;
}
}
return -1;
}
/**
* Add colorization on the pairs currently displayed
* based on the length/color of the flags.
* Small flags are "painted" at the end to ensure
* better visibility (not masked by wide flags).
*/
colorizeFlag(reset) {
if (!this.showFlags) {
return;
}
if (typeof reset === 'undefined') {
reset = false;
}
var list = [].slice.call(this.listContent.children);
if (reset) {
for (var i = 0 ; i < list.length ; i++) {
list[i].backgroundColor = 'none';
}
}
// Retrieving all flags with length greater than 2 sorted (small at end)
this.nav.getFlags(2, (flags) => {
for (var j = 0 ; j < flags.length ; j++) {
var end = false;
var initialLine = this.indexOfLine_(flags[j].start);
if (initialLine === -1) {
console.log('Undefined flag offset');
return;
}
var initialByte = flags[j].start - list[initialLine].offset.start;
// We walk through lines
for (var i = initialLine ; i < list.length && !end ; i++) {
// If it's a "flag line" we move on the next
if (typeof list[i].offset === 'undefined' || list[i].classList.contains('flag')) {
continue;
}
var hexList = list[i].children[1].children;
for (var x = initialByte ; x < hexList.length ; x++) {
// If reach the end, we stop here
if (hexList[x].offset === flags[j].end) {
end = true;
break;
}
// We color the byte
hexList[x].style.backgroundColor = this.getFlagColor(flags[j].name);
}
initialByte = 0;
}
}
});
}
//#endregion
}