mirror of
https://github.com/radareorg/radare2-webui.git
synced 2026-11-07 06:36:01 -10:00
Modernize and update the material webui
This commit is contained in:
1 parent
0bfc0c04ad
commit
4477963002
24 files changed
+389
-229
No files matched your search
+62
-45
@@ -957,53 +957,70 @@ function has_scrollbar(divnode) {
|
||||
}
|
||||
|
||||
function on_scroll(event) {
|
||||
// console.log($(event.target).scrollTop());
|
||||
if (!r2ui._dis.scrolling) {
|
||||
var enyo = $('#radareApp').length ? true : false;
|
||||
var panel_disas = false;
|
||||
if (!enyo) panel_disas = $('#main_panel').tabs('option', 'active') === 0 ? true : false;
|
||||
r2ui._dis.scrolling = true;
|
||||
if (r2ui._dis.display == 'flat' && (enyo || panel_disas)) {
|
||||
var scroll_offset = null;
|
||||
var top_offset = null;
|
||||
var addr = null;
|
||||
if (enyo) {
|
||||
scroll_offset = $('#main_panel').scrollTop();
|
||||
top_offset = $('#gbox').height() - $('#main_panel').height() - 10;
|
||||
container_element = $('#center_panel');
|
||||
} else {
|
||||
scroll_offset = $('#center_panel').scrollTop();
|
||||
top_offset = $('#gbox').height() - $('#center_panel').height() - 10;
|
||||
container_element = $('#disasm_tab');
|
||||
}
|
||||
if (has_scrollbar($('#center_panel')[0])) {
|
||||
if (scroll_offset === 0) {
|
||||
addr = '0x' + r2ui._dis.instructions[0].addr.toString(16);
|
||||
// console.log("Scroll en top", scroll_offset, top_offset, addr);
|
||||
r2.get_disasm_before(addr, 50, function(x) {
|
||||
// console.log(x.length);
|
||||
r2ui._dis.instructions = x.concat(r2ui._dis.instructions);
|
||||
});
|
||||
container_element.html('<div id=\'canvas\' class=\'canvas enyo-selectable ec_gui_background\'></div>');
|
||||
render_instructions(r2ui._dis.instructions);
|
||||
scroll_to_address(addr);
|
||||
rehighlight_iaddress(r2ui._dis.selected_offset);
|
||||
} else if (scroll_offset > top_offset) {
|
||||
// console.log("Scroll en top", scroll_offset, top_offset)
|
||||
addr = '0x' + r2ui._dis.instructions[r2ui._dis.instructions.length - 1].addr.toString(16);
|
||||
r2.get_disasm_after(addr, 100, function(x) {
|
||||
r2ui._dis.instructions = r2ui._dis.instructions.slice(0, -1).concat(x);
|
||||
});
|
||||
container_element.html('<div id=\'canvas\' class=\'canvas enyo-selectable ec_gui_background\'></div>');
|
||||
render_instructions(r2ui._dis.instructions);
|
||||
scroll_to_address(addr);
|
||||
rehighlight_iaddress(r2ui._dis.selected_offset);
|
||||
}
|
||||
}
|
||||
if (r2ui._dis.scrolling) return;
|
||||
if (r2ui._dis.display != 'flat') return;
|
||||
var enyo = $('#radareApp').length ? true : false;
|
||||
if (!enyo && $('#main_panel').tabs('option', 'active') !== 0) return;
|
||||
var ins = r2ui._dis.instructions;
|
||||
if (!ins || ins.length === 0) return;
|
||||
var container = enyo ? $('#main_panel') : $('#center_panel');
|
||||
var content = enyo ? $('#center_panel') : $('#disasm_tab');
|
||||
var el = container[0];
|
||||
if (!has_scrollbar(el)) return;
|
||||
|
||||
// Start fetching before the edge is reached so scrolling feels continuous
|
||||
var margin = 400;
|
||||
var at_top = el.scrollTop < margin;
|
||||
var at_bottom = el.scrollTop + el.clientHeight > el.scrollHeight - margin;
|
||||
if (!at_top && !at_bottom) return;
|
||||
r2ui._dis.scrolling = true;
|
||||
|
||||
// Remember the first visible instruction and its viewport position so the
|
||||
// re-render is pixel-stable instead of jumping around
|
||||
var crect = el.getBoundingClientRect();
|
||||
var anchor = null;
|
||||
var anchor_delta = 0;
|
||||
$(el).find('.insaddr').each(function() {
|
||||
var r = this.getBoundingClientRect();
|
||||
if (r.bottom >= crect.top) {
|
||||
anchor = get_address_from_class(this);
|
||||
anchor_delta = r.top - crect.top;
|
||||
return false;
|
||||
}
|
||||
r2ui._dis.scrolling = false;
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Keep a bounded sliding window so re-render cost stays constant
|
||||
var MAX_INSTRUCTIONS = 600;
|
||||
var fetched = 0;
|
||||
var addr;
|
||||
if (at_top) {
|
||||
addr = '0x' + ins[0].addr.toString(16);
|
||||
r2.get_disasm_before(addr, 100, function(x) {
|
||||
fetched = x.length;
|
||||
ins = x.concat(ins);
|
||||
});
|
||||
if (ins.length > MAX_INSTRUCTIONS) ins = ins.slice(0, MAX_INSTRUCTIONS);
|
||||
} else {
|
||||
addr = '0x' + ins[ins.length - 1].addr.toString(16);
|
||||
r2.get_disasm_after(addr, 100, function(x) {
|
||||
fetched = x.length - 1; // first entry duplicates the last one shown
|
||||
ins = ins.slice(0, -1).concat(x);
|
||||
});
|
||||
if (ins.length > MAX_INSTRUCTIONS) ins = ins.slice(ins.length - MAX_INSTRUCTIONS);
|
||||
}
|
||||
if (fetched <= 0) { // hit the beginning/end of the file, nothing to do
|
||||
r2ui._dis.scrolling = false;
|
||||
return;
|
||||
}
|
||||
r2ui._dis.instructions = ins;
|
||||
content.html('<div id=\'canvas\' class=\'canvas enyo-selectable ec_gui_background\'></div>');
|
||||
render_instructions(ins);
|
||||
if (anchor !== null) {
|
||||
var a = $(el).find('.insaddr.addr_' + anchor)[0];
|
||||
if (a) el.scrollTop += a.getBoundingClientRect().top - crect.top - anchor_delta;
|
||||
}
|
||||
rehighlight_iaddress(r2ui._dis.selected_offset);
|
||||
r2ui._dis.scrolling = false;
|
||||
}
|
||||
|
||||
function scroll_to_element(element) {
|
||||
|
||||
+4
-4
@@ -521,11 +521,11 @@ r2.getTextLogger = function(obj) {
|
||||
for (let i = 0; i < ret.length; i++) {
|
||||
var message = ret[i];
|
||||
obj.events['message']({
|
||||
'id': message[0],
|
||||
'text': message[1]
|
||||
'id': message.id,
|
||||
'text': message.msg
|
||||
});
|
||||
if (message[0] > obj.last) {
|
||||
obj.last = message[0];
|
||||
if (message.id > obj.last) {
|
||||
obj.last = message.id;
|
||||
}
|
||||
}
|
||||
if (cb) {
|
||||
|
||||
@@ -195,6 +195,7 @@ r2ui.seek = function(addr, push, scroll) {
|
||||
r2.cmd('s ' + addr, function() {
|
||||
r2ui._dis.seek(addr, scroll);
|
||||
r2ui._hex.seek(addr, scroll);
|
||||
if (r2ui._dec) r2ui._dec.seek(addr);
|
||||
});
|
||||
// if (r2ui._dis.tmp_address !== undefined) r2ui._dis.tmp_address = addr;
|
||||
};
|
||||
|
||||
@@ -8,6 +8,14 @@ tr.active {
|
||||
max-width: 24em;
|
||||
}
|
||||
|
||||
/* Tables fit the widget width and resize with the window */
|
||||
.rwidget .mdl-data-table {
|
||||
width: calc(100% - 1em);
|
||||
margin: 0.5em;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
th.sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
height:100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrain the widget slots to the viewport so each .rwidget is the
|
||||
* real scroll container; required for the sticky toolbars to work.
|
||||
*/
|
||||
#content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.rwidget {
|
||||
height:100%;
|
||||
overflow:auto;
|
||||
@@ -15,6 +23,54 @@
|
||||
color: white;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common widget toolbar: sticky on top of the scrolling widget with a
|
||||
* solid background so the content never shows through, in both the
|
||||
* full and the splitted layouts.
|
||||
*/
|
||||
.widget-toolbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 90;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding: 0.25em 0.5em;
|
||||
background-color: #fafafa;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.rwidget.dark .widget-toolbar {
|
||||
background-color: #2b2b2b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.widget-toolbar .toolbar-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.widget-toolbar .table-filter {
|
||||
margin: 3px;
|
||||
flex: 1 1 10em;
|
||||
max-width: 24em;
|
||||
height: 28px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.3);
|
||||
border-radius: 2px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.widget-select {
|
||||
margin: 3px;
|
||||
height: 28px;
|
||||
padding: 0 4px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.3);
|
||||
border-radius: 2px;
|
||||
background-color: white;
|
||||
color: black;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.rwidget.full {
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -22,6 +22,7 @@
|
||||
<title>Radare2 Material Design</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=yes">
|
||||
|
||||
<link rel="icon" href="./images/icon.png">
|
||||
<link rel="stylesheet" href="./vendors/material.min.css">
|
||||
<link rel="stylesheet" href="./stylesheet.css">
|
||||
</head>
|
||||
@@ -100,14 +101,13 @@
|
||||
<a id='menu_hexdump' class="mdl-navigation__link"><i class="mdl-color-text--blue-grey-400 material-icons mi-dashboard"></i>Hexdump</a>
|
||||
<a id='menu_debug' class="mdl-navigation__link"><i class="mdl-color-text--blue-grey-400 material-icons mi-play_circle_outline"></i>Debugger</a>
|
||||
<a id='menu_functions' class="mdl-navigation__link"><i class="mdl-color-text--blue-grey-400 material-icons mi-list"></i>Functions</a>
|
||||
<a id='menu_classes' class="mdl-navigation__link"><i class="mdl-color-text--blue-grey-400 material-icons mi-list"></i>Classes</a>
|
||||
<a id='menu_flags' class="mdl-navigation__link"><i class="mdl-color-text--blue-grey-400 material-icons mi-flag"></i>Flags</a>
|
||||
<a id='menu_search' class="mdl-navigation__link"><i class="mdl-color-text--blue-grey-400 material-icons mi-search"></i>Search</a>
|
||||
<a id='menu_script' class="mdl-navigation__link"><i class="mdl-color-text--blue-grey-400 material-icons mi-extension"></i>Script</a>
|
||||
<a id='menu_comments' class="mdl-navigation__link"><i class="mdl-color-text--blue-grey-400 material-icons mi-forum"></i>Comments</a>
|
||||
<div class="mdl-layout-spacer">-</div>
|
||||
<a id='menu_settings2' class="mdl-navigation__link"><i class="mdl-color-text--blue-grey-400 material-icons mi-settings"></i>Settings</a>
|
||||
<a id='menu_help' class="mdl-navigation__link"><i class="mdl-color-text--blue-grey-400 material-icons mi-help_outline"></i>Help</a>
|
||||
<div class="mdl-layout-spacer"><br /><br /><br /></div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ function ready() {
|
||||
onClick('menu_search', () => uiContext.navigateTo(Widgets.SEARCH));
|
||||
onClick('menu_script', () => uiContext.navigateTo(Widgets.SCRIPTS));
|
||||
onClick('menu_comments', () => uiContext.navigateTo(Widgets.COMMENTS));
|
||||
onClick('menu_help', () => alert('todo'));
|
||||
onClick('menu_help', () => window.open('https://book.rada.re/', '_blank'));
|
||||
|
||||
// Left sub-menu
|
||||
// onClick('menu_project_save', saveProject);
|
||||
|
||||
@@ -47,9 +47,34 @@ function iconButton(iconName, title, onclick = null) {
|
||||
return button;
|
||||
}
|
||||
|
||||
function selectInput(title, options, onchange = null, selected = null) {
|
||||
const select = document.createElement('select');
|
||||
select.className = 'widget-select';
|
||||
select.title = title;
|
||||
select.setAttribute('aria-label', title);
|
||||
for (const option of options) {
|
||||
const node = document.createElement('option');
|
||||
node.value = option;
|
||||
node.textContent = option;
|
||||
if (option === selected) node.selected = true;
|
||||
select.appendChild(node);
|
||||
}
|
||||
if (onchange !== null) select.addEventListener('change', () => onchange(select.value));
|
||||
return select;
|
||||
}
|
||||
|
||||
function toolbar(...children) {
|
||||
const bar = document.createElement('div');
|
||||
bar.className = 'widget-toolbar';
|
||||
for (const child of children) bar.appendChild(child);
|
||||
return bar;
|
||||
}
|
||||
|
||||
export const Inputs = {
|
||||
button: inputButton,
|
||||
imgButton: imgButton,
|
||||
iconButton: iconButton,
|
||||
pictogramInputButton: pictogramInputButton
|
||||
pictogramInputButton: pictogramInputButton,
|
||||
select: selectInput,
|
||||
toolbar: toolbar
|
||||
};
|
||||
@@ -43,6 +43,7 @@ export class Table {
|
||||
this.cols[c] = this.cols[c].substr(1);
|
||||
} else if (this.cols[c][0] === '~') {
|
||||
this.contentEditable[c] = true;
|
||||
this.cols[c] = this.cols[c].substr(1);
|
||||
}
|
||||
|
||||
var th = document.createElement('th');
|
||||
@@ -115,7 +116,11 @@ export class Table {
|
||||
return tr;
|
||||
}
|
||||
|
||||
insertInto(node) {
|
||||
/**
|
||||
* Insert the table into node; when filterParent is provided (usually
|
||||
* a widget toolbar) the filter input is appended there instead.
|
||||
*/
|
||||
insertInto(node, filterParent = null) {
|
||||
if (this.id !== false) {
|
||||
const filter = document.createElement('input');
|
||||
filter.className = 'table-filter';
|
||||
@@ -128,7 +133,7 @@ export class Table {
|
||||
row.hidden = !row.textContent.toLowerCase().includes(query);
|
||||
}
|
||||
});
|
||||
node.appendChild(filter);
|
||||
(filterParent || node).appendChild(filter);
|
||||
|
||||
for (const [column, heading] of [...this.thead.rows[0].cells].entries()) {
|
||||
heading.classList.add('sortable');
|
||||
|
||||
@@ -3,9 +3,8 @@ export class FortunesCard {
|
||||
get DOM() { return this.card; }
|
||||
|
||||
constructor() {
|
||||
this.currentFortune = this.getNewFortune();
|
||||
|
||||
this.build();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
build() {
|
||||
@@ -18,7 +17,6 @@ export class FortunesCard {
|
||||
|
||||
this.fortuneBlock = document.createElement('div');
|
||||
this.fortuneBlock.className = 'mdl-card__supporting-text mdl-color-text--grey-600';
|
||||
this.fortuneBlock.innerHTML = this.currentFortune;
|
||||
|
||||
const action = document.createElement('div');
|
||||
action.className = 'mdl-card__actions mdl-card--border';
|
||||
@@ -36,15 +34,8 @@ export class FortunesCard {
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.currentFortune = this.getNewFortune();
|
||||
this.fortuneBlock.innerHTML = this.currentFortune;
|
||||
}
|
||||
|
||||
getNewFortune() {
|
||||
let fortune;
|
||||
r2.cmd('fo', function(d) {
|
||||
fortune = d;
|
||||
r2.cmd('fo', (fortune) => {
|
||||
this.fortuneBlock.textContent = fortune.trim();
|
||||
});
|
||||
return fortune;
|
||||
}
|
||||
}
|
||||
@@ -25,13 +25,6 @@ export class BasePreWidget extends BaseWidget {
|
||||
|
||||
/** @override */
|
||||
init() {
|
||||
if (typeof this.backButton === 'undefined') {
|
||||
return;
|
||||
}
|
||||
this.backButton.style.position = 'absolute';
|
||||
this.backButton.style.top = '1em';
|
||||
this.backButton.style.left = '1em';
|
||||
|
||||
r2Wrapper.registerListener(R2Actions.SEEK, () => {
|
||||
if (this.displayed) {
|
||||
this.draw();
|
||||
@@ -41,8 +34,9 @@ export class BasePreWidget extends BaseWidget {
|
||||
|
||||
/** @override */
|
||||
draw() {
|
||||
this.node.innerHTML = '';
|
||||
if (typeof this.backButton !== 'undefined') {
|
||||
this.node.appendChild(this.backButton);
|
||||
this.node.appendChild(Inputs.toolbar(this.backButton));
|
||||
}
|
||||
this.node.appendChild(this.getPre());
|
||||
}
|
||||
@@ -50,8 +44,13 @@ export class BasePreWidget extends BaseWidget {
|
||||
/** Format text from registered r2cmd and provides a <pre> element */
|
||||
getPre() {
|
||||
const pre = document.createElement('pre');
|
||||
pre.style.margin = '0.5em';
|
||||
r2.cmd(this.r2cmd, output => {
|
||||
pre.appendChild(this.formatFunc(output));
|
||||
if (output.trim().length === 0) {
|
||||
pre.textContent = 'No output for "' + this.r2cmd.split('|')[0] + '" at the current offset.';
|
||||
} else {
|
||||
pre.appendChild(this.formatFunc(output));
|
||||
}
|
||||
});
|
||||
return pre;
|
||||
}
|
||||
|
||||
@@ -29,39 +29,41 @@ export class ClassesWidget extends BaseWidget {
|
||||
getPanel() {
|
||||
var c = document.createElement('div');
|
||||
|
||||
var header = document.createElement('div');
|
||||
header.style.position = 'fixed';
|
||||
header.style.margin = '0.5em';
|
||||
var header = Inputs.toolbar(
|
||||
Inputs.button('Refresh', () => {
|
||||
statusMessage('Analyzing symbols...');
|
||||
r2.cmd('aa', () => {
|
||||
statusMessage('done');
|
||||
this.draw();
|
||||
});
|
||||
}));
|
||||
c.appendChild(header);
|
||||
|
||||
header.appendChild(Inputs.button('Refresh', () => {
|
||||
statusMessage('Analyzing symbols...');
|
||||
r2.cmd('aa', () => {
|
||||
statusMessage('done');
|
||||
this.draw();
|
||||
});
|
||||
}));
|
||||
|
||||
var content = document.createElement('div');
|
||||
content.style.paddingTop = '70px';
|
||||
c.appendChild(content);
|
||||
|
||||
r2.cmd('ic', function(d) {
|
||||
r2.cmd('icj', function(d) {
|
||||
var data = [];
|
||||
try {
|
||||
data = JSON.parse(d);
|
||||
} catch (e) {
|
||||
console.error('classes: unexpected output', e);
|
||||
}
|
||||
var table = new Table(
|
||||
['+Address', 'Type', 'Name'],
|
||||
['+Address', 'Name', 'Methods'],
|
||||
[false, true, false],
|
||||
'classesTable',
|
||||
null,
|
||||
Widgets.CLASSES);
|
||||
Widgets.DISASSEMBLY);
|
||||
|
||||
var lines = d.split(/\n/); //clickable offsets (d).split (/\n/);
|
||||
for (var i in lines) {
|
||||
var items = lines[i].match(/^(0x[0-9a-f]+)\s+([0-9]+)\s+([0-9]+(\s+\->\s+[0-9]+)?)\s+(.+)$/);
|
||||
if (items !== null) {
|
||||
table.addRow([items[1], items[5], items[2]]);
|
||||
}
|
||||
}
|
||||
table.insertInto(content);
|
||||
data.forEach(x => {
|
||||
table.addRow([
|
||||
'0x' + (x.addr || 0).toString(16),
|
||||
x.classname || x.name,
|
||||
(x.methods || []).length
|
||||
]);
|
||||
});
|
||||
table.insertInto(content, header);
|
||||
});
|
||||
|
||||
return c;
|
||||
|
||||
@@ -27,20 +27,16 @@ export class CommentsWidget extends BaseWidget {
|
||||
getPanel() {
|
||||
var c = document.createElement('div');
|
||||
|
||||
var header = document.createElement('div');
|
||||
header.style.position = 'fixed';
|
||||
header.style.margin = '0.5em';
|
||||
c.appendChild(header);
|
||||
|
||||
header.appendChild(Inputs.button('Notes', () => uiContext.navigateTo(Widgets.NOTES)));
|
||||
var toolbar = Inputs.toolbar(
|
||||
Inputs.button('Notes', () => uiContext.navigateTo(Widgets.NOTES)));
|
||||
c.appendChild(toolbar);
|
||||
|
||||
var content = document.createElement('div');
|
||||
content.style.paddingTop = '70px';
|
||||
c.appendChild(content);
|
||||
|
||||
r2.cmd('CC', (d) => {
|
||||
var table = new Table(
|
||||
['+Offset', '~Comment'],
|
||||
['+Address', '~Comment'],
|
||||
[true, false],
|
||||
'commentsTable',
|
||||
(row, newVal) => {
|
||||
@@ -54,7 +50,7 @@ export class CommentsWidget extends BaseWidget {
|
||||
|
||||
this.draw();
|
||||
},
|
||||
Widgets.HEXDUMP);
|
||||
Widgets.DISASSEMBLY);
|
||||
|
||||
var lines = d.split(/\n/); //clickable offsets (d).split (/\n/);
|
||||
for (var i in lines) {
|
||||
@@ -63,7 +59,7 @@ export class CommentsWidget extends BaseWidget {
|
||||
table.addRow([line[0], line[1]]);
|
||||
}
|
||||
}
|
||||
table.insertInto(content);
|
||||
table.insertInto(content, toolbar);
|
||||
});
|
||||
|
||||
return c;
|
||||
|
||||
@@ -31,13 +31,8 @@ export class DebuggerWidget extends BaseWidget {
|
||||
|
||||
getPanel() {
|
||||
var c = document.createElement('div');
|
||||
if (this.inColor) {
|
||||
c.style.backgroundColor = '#202020';
|
||||
}
|
||||
|
||||
var header = document.createElement('div');
|
||||
header.style.position = 'fixed';
|
||||
header.style.margin = '0.5em';
|
||||
var header = Inputs.toolbar();
|
||||
c.appendChild(header);
|
||||
|
||||
header.appendChild(Inputs.iconButton('keyboard_arrow_up', 'UP', () => r2.cmd('s--', () => this.draw())));
|
||||
@@ -57,7 +52,6 @@ export class DebuggerWidget extends BaseWidget {
|
||||
}));
|
||||
|
||||
var content = document.createElement('div');
|
||||
content.style.paddingTop = '50px';
|
||||
c.appendChild(content);
|
||||
|
||||
// stack
|
||||
|
||||
@@ -1,18 +1,85 @@
|
||||
import {BasePreWidget} from './BasePreWidget';
|
||||
import {BaseWidget} from './BaseWidget';
|
||||
import {Inputs} from '../helpers/Inputs';
|
||||
|
||||
import {uiContext} from '../core/UIContext';
|
||||
import {Widgets} from './Widgets';
|
||||
import {formatOffsets} from '../helpers/Format';
|
||||
import {r2Wrapper, R2Actions} from '../core/R2Wrapper';
|
||||
|
||||
const inColor = true; // TODO inColor
|
||||
|
||||
export class DisassemblyDecompileWidget extends BasePreWidget {
|
||||
export class DisassemblyDecompileWidget extends BaseWidget {
|
||||
constructor() {
|
||||
super(
|
||||
'Decompile',
|
||||
x => formatOffsets(x),
|
||||
'pdc|H',
|
||||
Inputs.iconButton('undo', 'Back to Disassembly', () => uiContext.navigateTo(Widgets.DISASSEMBLY)));
|
||||
super('Decompile', 'dark');
|
||||
}
|
||||
|
||||
init() {
|
||||
this.decompilers = ['pdc'];
|
||||
this.currentDecompiler = 'pdc';
|
||||
this.listFetched = false;
|
||||
|
||||
r2Wrapper.registerListener(R2Actions.SEEK, () => {
|
||||
if (this.displayed) {
|
||||
this.draw();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.node.innerHTML = '';
|
||||
|
||||
if (this.listFetched) {
|
||||
this.drawPanel();
|
||||
} else {
|
||||
// Available decompilers, same list as `e cmd.pdc=?` (pdc is builtin)
|
||||
r2.cmd('e cmd.pdc=?', (d) => {
|
||||
const list = d.trim().split(/\n/).map(x => x.trim()).filter(x => x.length > 0);
|
||||
if (list.length > 0) {
|
||||
this.decompilers = list.includes('pdc') ? list : ['pdc'].concat(list);
|
||||
}
|
||||
this.listFetched = true;
|
||||
this.drawPanel();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
drawPanel() {
|
||||
const toolbar = Inputs.toolbar(
|
||||
Inputs.iconButton('undo', 'Back to Disassembly', () => uiContext.navigateTo(Widgets.DISASSEMBLY)),
|
||||
Inputs.select('Decompiler', this.decompilers, (choice) => {
|
||||
this.currentDecompiler = choice;
|
||||
this.refreshOutput();
|
||||
}, this.currentDecompiler));
|
||||
|
||||
this.output = document.createElement('pre');
|
||||
this.output.style.margin = '0.5em';
|
||||
|
||||
this.node.appendChild(toolbar);
|
||||
this.node.appendChild(this.output);
|
||||
this.refreshOutput();
|
||||
}
|
||||
|
||||
refreshOutput() {
|
||||
this.output.innerHTML = 'Decompiling...';
|
||||
r2.cmd('e cmd.pdc=' + this.currentDecompiler, () => {
|
||||
r2.cmd('pdc|H', (d) => {
|
||||
this.output.innerHTML = '';
|
||||
if (d.trim().length === 0) {
|
||||
this.drawEmptyHint();
|
||||
} else {
|
||||
this.output.appendChild(formatOffsets(d));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** No output usually means there is no function analyzed here */
|
||||
drawEmptyHint() {
|
||||
const hint = document.createElement('div');
|
||||
const msg = document.createElement('p');
|
||||
msg.textContent = 'Nothing to decompile at the current offset. Analyzing the function may help.';
|
||||
hint.appendChild(msg);
|
||||
hint.appendChild(Inputs.button('Analyze function', () => {
|
||||
r2.cmd('af', () => this.refreshOutput());
|
||||
}));
|
||||
this.output.appendChild(hint);
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,6 @@ export class DisassemblyGraphWidget extends BaseWidget {
|
||||
|
||||
init() {
|
||||
this.backButton = Inputs.iconButton('undo', 'Back to Disassembly', () => uiContext.navigateTo(Widgets.DISASSEMBLY));
|
||||
this.backButton.style.position = 'absolute';
|
||||
this.backButton.style.top = '1em';
|
||||
this.backButton.style.left = '1em';
|
||||
|
||||
r2Wrapper.registerListener(R2Actions.SEEK, () => {
|
||||
if (this.displayed) {
|
||||
@@ -27,7 +24,8 @@ export class DisassemblyGraphWidget extends BaseWidget {
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.node.appendChild(this.backButton);
|
||||
this.node.innerHTML = '';
|
||||
this.node.appendChild(Inputs.toolbar(this.backButton));
|
||||
this.node.appendChild(this.getGraph());
|
||||
}
|
||||
|
||||
@@ -42,8 +40,13 @@ export class DisassemblyGraphWidget extends BaseWidget {
|
||||
var tail = inColor ? '|H': '';
|
||||
r2.cmd('agf' + tail, (d) => {
|
||||
const pre = document.createElement('pre');
|
||||
pre.style.margin = '0.5em';
|
||||
pre.style.color = inColor ? 'white' : 'black';
|
||||
pre.appendChild(formatOffsets(d))
|
||||
if (d.trim().length === 0) {
|
||||
pre.textContent = 'No graph at the current offset, analyze the function first.';
|
||||
} else {
|
||||
pre.appendChild(formatOffsets(d))
|
||||
}
|
||||
graph.appendChild(pre);
|
||||
});
|
||||
|
||||
|
||||
@@ -31,24 +31,18 @@ export class FlagsSpacesWidget extends BaseWidget {
|
||||
|
||||
getPanel() {
|
||||
var c = document.createElement('div');
|
||||
if (this.inColor) {
|
||||
c.style.backgroundColor = '#202020';
|
||||
}
|
||||
|
||||
var header = document.createElement('div');
|
||||
header.style.position = 'fixed';
|
||||
header.style.margin = '0.5em';
|
||||
var header = Inputs.toolbar(
|
||||
Inputs.iconButton('undo', 'Back to flags', () => uiContext.navigateTo(Widgets.FLAGS)),
|
||||
Inputs.button('Deselect', () => { this.current = null; r2.cmd('fs *', () => this.draw()); } ),
|
||||
Inputs.button('Add', () => this.setFlagspace()),
|
||||
Inputs.button('Delete', () => this.delFlagspace()),
|
||||
Inputs.button('Rename', () => this.renameFlagspace()));
|
||||
c.appendChild(header);
|
||||
|
||||
header.appendChild(Inputs.iconButton('undo', 'Back to flags', () => uiContext.navigateTo(Widgets.FLAGS)));
|
||||
header.appendChild(Inputs.button('Deselect', () => { this.current = null; r2.cmd('fs *', () => this.draw()); } ));
|
||||
header.appendChild(Inputs.button('Add', () => this.setFlagspace()));
|
||||
header.appendChild(Inputs.button('Delete', () => this.delFlagspace()));
|
||||
header.appendChild(Inputs.button('Rename', () => this.renameFlagspace()));
|
||||
|
||||
var content = document.createElement('div');
|
||||
content.appendChild(document.createTextNode('Click on a row to select it.'));
|
||||
content.style.paddingTop = '70px';
|
||||
content.style.margin = '0.5em';
|
||||
c.appendChild(content);
|
||||
|
||||
r2.cmd('fsj', (d) => {
|
||||
@@ -93,7 +87,7 @@ export class FlagsSpacesWidget extends BaseWidget {
|
||||
this.current = x.name;
|
||||
});
|
||||
});
|
||||
table.insertInto(content);
|
||||
table.insertInto(content, header);
|
||||
});
|
||||
|
||||
return c;
|
||||
|
||||
@@ -28,37 +28,38 @@ export class FlagsWidget extends BaseWidget {
|
||||
|
||||
getPanel() {
|
||||
var c = document.createElement('div');
|
||||
if (this.inColor) {
|
||||
c.style.backgroundColor = '#202020';
|
||||
}
|
||||
|
||||
var header = document.createElement('div');
|
||||
header.style.position = 'fixed';
|
||||
header.style.margin = '0.5em';
|
||||
c.appendChild(header);
|
||||
|
||||
|
||||
header.appendChild(Inputs.button('Spaces', () => uiContext.navigateTo(Widgets.FLAGS_SPACE)));
|
||||
header.appendChild(Inputs.button('Delete All', () => r2.cmd('f-*', () => this.draw())));
|
||||
var toolbar = Inputs.toolbar(
|
||||
Inputs.button('Spaces', () => uiContext.navigateTo(Widgets.FLAGS_SPACE)),
|
||||
Inputs.button('Delete All', () => {
|
||||
if (confirm('Delete all flags?')) {
|
||||
r2.cmd('f-*', () => this.draw());
|
||||
}
|
||||
}));
|
||||
c.appendChild(toolbar);
|
||||
|
||||
var content = document.createElement('div');
|
||||
content.style.paddingTop = '50px';
|
||||
c.appendChild(content);
|
||||
|
||||
r2.cmd('fj', function(d) {
|
||||
let data = JSON.parse(d);
|
||||
r2.cmd('fj', (d) => {
|
||||
let data = [];
|
||||
try {
|
||||
data = JSON.parse(d);
|
||||
} catch (e) {
|
||||
console.error('flags: unexpected output', e);
|
||||
}
|
||||
var table = new Table(
|
||||
['+Offset', 'Size', 'Name'],
|
||||
['+Address', 'Size', 'Name'],
|
||||
[true, true, false],
|
||||
'flagsTable',
|
||||
null,
|
||||
Widgets.HEXDUMP);
|
||||
|
||||
data.map(x => {
|
||||
table.addRow(['0x'+x.offset.toString(16), x.size, x.name])
|
||||
//table.addRow(Object.values(x))
|
||||
const addr = (typeof x.addr !== 'undefined') ? x.addr : x.offset;
|
||||
table.addRow(['0x'+addr.toString(16), x.size, x.name])
|
||||
});
|
||||
table.insertInto(content);
|
||||
table.insertInto(content, toolbar);
|
||||
});
|
||||
|
||||
return c;
|
||||
|
||||
@@ -30,9 +30,7 @@ export class FunctionsWidget extends BaseWidget {
|
||||
getPanel() {
|
||||
var c = document.createElement('div');
|
||||
|
||||
var header = document.createElement('div');
|
||||
header.style.position = 'fixed';
|
||||
header.style.margin = '0.5em';
|
||||
var header = Inputs.toolbar();
|
||||
c.appendChild(header);
|
||||
|
||||
header.appendChild(Inputs.button('Symbols', () => {
|
||||
@@ -76,10 +74,15 @@ export class FunctionsWidget extends BaseWidget {
|
||||
}));
|
||||
|
||||
var content = document.createElement('div');
|
||||
content.style.paddingTop = '70px';
|
||||
c.appendChild(content);
|
||||
|
||||
r2.cmd('afl', function(d) {
|
||||
r2.cmd('aflj', function(d) {
|
||||
var data = [];
|
||||
try {
|
||||
data = JSON.parse(d);
|
||||
} catch (e) {
|
||||
console.error('functions: unexpected output', e);
|
||||
}
|
||||
var table = new Table(
|
||||
['+Address', 'Name', 'Size', 'CC'],
|
||||
[false, true, false, false],
|
||||
@@ -87,14 +90,10 @@ export class FunctionsWidget extends BaseWidget {
|
||||
null,
|
||||
Widgets.DISASSEMBLY);
|
||||
|
||||
var lines = d.split(/\n/); //clickable offsets (d).split (/\n/);
|
||||
for (var i in lines) {
|
||||
var items = lines[i].match(/^(0x[0-9a-f]+)\s+([0-9]+)\s+([0-9]+(\s+\->\s+[0-9]+)?)\s+(.+)$/);
|
||||
if (items !== null) {
|
||||
table.addRow([items[1], items[5], items[2], items[3]]);
|
||||
}
|
||||
}
|
||||
table.insertInto(content);
|
||||
data.forEach(x => {
|
||||
table.addRow(['0x' + x.addr.toString(16), x.name, x.realsz || x.size, x.cc]);
|
||||
});
|
||||
table.insertInto(content, header);
|
||||
});
|
||||
|
||||
return c;
|
||||
|
||||
@@ -19,22 +19,29 @@ export class NotesWidget extends BaseWidget {
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.node.innerHTML = '';
|
||||
this.node.appendChild(this.getPanel());
|
||||
}
|
||||
|
||||
getPanel() {
|
||||
var c = document.createElement('div');
|
||||
|
||||
var header = document.createElement('div');
|
||||
header.style.position = 'fixed';
|
||||
header.style.margin = '0.5em';
|
||||
c.appendChild(header);
|
||||
|
||||
header.appendChild(Inputs.iconButton('undo', 'Back to Comments', () => uiContext.navigateTo(Widgets.COMMENTS)));
|
||||
c.appendChild(Inputs.toolbar(
|
||||
Inputs.iconButton('undo', 'Back to Comments', () => uiContext.navigateTo(Widgets.COMMENTS))));
|
||||
|
||||
var content = document.createElement('div');
|
||||
content.style.paddingTop = '70px';
|
||||
content.innerHTML = '<textarea rows=32 style="width:100%;height:100%"></textarea>';
|
||||
content.style.margin = '0.5em';
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.rows = 32;
|
||||
textarea.style.width = '100%';
|
||||
textarea.style.boxSizing = 'border-box';
|
||||
textarea.placeholder = 'Write your notes here, they are saved automatically.';
|
||||
textarea.value = localStorage.getItem('notes') || '';
|
||||
textarea.addEventListener('input', () => {
|
||||
localStorage.setItem('notes', textarea.value);
|
||||
});
|
||||
content.appendChild(textarea);
|
||||
c.appendChild(content);
|
||||
|
||||
return c;
|
||||
|
||||
@@ -20,24 +20,26 @@ export class ScriptWidget extends BaseWidget {
|
||||
|
||||
draw() {
|
||||
this.toggleFoo = '';
|
||||
this.node.innerHTML = '';
|
||||
this.node.appendChild(this.getPanel());
|
||||
}
|
||||
|
||||
getPanel() {
|
||||
var c = document.createElement('div');
|
||||
|
||||
c.appendChild(Inputs.button('Run', () => this.runScript()));
|
||||
c.appendChild(Inputs.button('Indent', () => this.indentScript()));
|
||||
c.appendChild(Inputs.button('Output', () => this.toggleScriptOutput()));
|
||||
// c.appendChild(Inputs.button('Console', () => uiContext.navigateTo(Widgets.CONSOLE)));
|
||||
|
||||
c.appendChild(document.createElement('br'));
|
||||
c.appendChild(Inputs.toolbar(
|
||||
Inputs.button('Run', () => this.runScript()),
|
||||
Inputs.button('Output', () => this.toggleScriptOutput())));
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.id = 'script';
|
||||
textarea.rows = 20;
|
||||
textarea.className = 'pre';
|
||||
textarea.style.width = '100%';
|
||||
textarea.style.boxSizing = 'border-box';
|
||||
textarea.addEventListener('input', () => {
|
||||
localStorage.setItem('script', textarea.value);
|
||||
});
|
||||
c.appendChild(textarea);
|
||||
|
||||
c.appendChild(document.createElement('br'));
|
||||
@@ -69,13 +71,6 @@ export class ScriptWidget extends BaseWidget {
|
||||
}
|
||||
}
|
||||
|
||||
indentScript() {
|
||||
var str = document.getElementById('script').value;
|
||||
var indented = /* NOT DEFINED js_beautify*/ (str);
|
||||
document.getElementById('script').value = indented;
|
||||
localStorage.script = indented;
|
||||
}
|
||||
|
||||
runScript() {
|
||||
var str = document.getElementById('script').value;
|
||||
localStorage.script = str;
|
||||
|
||||
@@ -23,36 +23,33 @@ export class SearchWidget extends BaseWidget {
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.node.innerHTML = '';
|
||||
this.node.appendChild(this.getPanel());
|
||||
}
|
||||
|
||||
getPanel() {
|
||||
const c = document.createElement('div');
|
||||
var header = document.createElement('div');
|
||||
header.style.margin = '0.5em';
|
||||
c.appendChild(header);
|
||||
|
||||
const form = document.createElement('input');
|
||||
form.id = 'search_input';
|
||||
form.className = 'mdl-card--expand mdl-textfield__input';
|
||||
form.style.backgroundColor = 'white';
|
||||
form.style.paddingLeft = '10px';
|
||||
form.style.top = '3.5em';
|
||||
form.style.height = '1.8em';
|
||||
form.className = 'table-filter';
|
||||
form.type = 'search';
|
||||
form.placeholder = 'Hex pairs, "string" or expression...';
|
||||
form.setAttribute('aria-label', 'Search query');
|
||||
form.addEventListener('keypress', (e) => this.searchKey(e.keyCode));
|
||||
|
||||
header.appendChild(form);
|
||||
header.appendChild(document.createElement('br'));
|
||||
|
||||
header.appendChild(Inputs.button('Hex', () => this.runSearch()));
|
||||
header.appendChild(Inputs.button('String', () => this.runSearchString()));
|
||||
header.appendChild(Inputs.button('Code', () => this.runSearchCode()));
|
||||
header.appendChild(Inputs.button('ROP', () => this.runSearchROP()));
|
||||
header.appendChild(Inputs.button('Magic', () => this.runSearchMagic()));
|
||||
const header = Inputs.toolbar(
|
||||
form,
|
||||
Inputs.button('Hex', () => this.runSearch()),
|
||||
Inputs.button('String', () => this.runSearchString()),
|
||||
Inputs.button('Code', () => this.runSearchCode()),
|
||||
Inputs.button('ROP', () => this.runSearchROP()),
|
||||
Inputs.button('Magic', () => this.runSearchMagic()));
|
||||
c.appendChild(header);
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.id = 'search_output';
|
||||
content.style.paddingTop = '50px';
|
||||
content.style.margin = '0.5em';
|
||||
content.style.color = 'black';
|
||||
content.className = 'pre';
|
||||
c.appendChild(content);
|
||||
@@ -61,10 +58,8 @@ export class SearchWidget extends BaseWidget {
|
||||
}
|
||||
|
||||
searchKey(keyCode) {
|
||||
var inp = document.getElementById('search_input');
|
||||
if (keyCode === 13) {
|
||||
this.runSearch(inp.value);
|
||||
inp.value = '';
|
||||
this.runSearch();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export class SettingsWidget extends BaseWidget {
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.node.innerHTML = '';
|
||||
this.grid = document.createElement('div');
|
||||
this.grid.className = 'mdl-grid';
|
||||
this.node.appendChild(this.grid);
|
||||
@@ -109,11 +110,6 @@ export class SettingsWidget extends BaseWidget {
|
||||
}
|
||||
|
||||
drawColors(dom) {
|
||||
var colors;
|
||||
r2.cmdj('ecoj', function(data) {
|
||||
colors = data;
|
||||
});
|
||||
|
||||
uiSwitch(dom, 'Colors', r2Settings.getItem(r2Settings.keys.COLORS), function(param, state) {
|
||||
r2Settings.setItem(r2Settings.keys.COLORS, state);
|
||||
});
|
||||
@@ -121,30 +117,35 @@ export class SettingsWidget extends BaseWidget {
|
||||
// Randomize
|
||||
uiActionButton(dom, function() {
|
||||
r2.cmd('ecr', function() {
|
||||
// TODO: tmp, should be replaced by event "color has changed"
|
||||
uiContext.widgetContainer.updateManagers.updates.apply();
|
||||
r2Wrapper.seek('$$');
|
||||
});
|
||||
}, 'Randomize');
|
||||
|
||||
// Set default
|
||||
uiActionButton(dom, function() {
|
||||
r2.cmd('ecd', function() {
|
||||
// TODO: tmp, should be replaced by event "color has changed"
|
||||
uiContext.widgetContainer.updateManagers.updates.apply();
|
||||
r2Wrapper.seek('$$');
|
||||
});
|
||||
}, 'Reset colors');
|
||||
|
||||
uiSelect(dom, 'Theme', colors, colors.indexOf(r2Settings.getItem(r2Settings.keys.THEME)), function(theme) {
|
||||
r2Settings.setItem(r2Settings.keys.THEME, theme);
|
||||
// The theme list comes asynchronously from r2
|
||||
r2.cmdj('ecoj', function(colors) {
|
||||
if (!Array.isArray(colors)) {
|
||||
return;
|
||||
}
|
||||
uiSelect(dom, 'Theme', colors, colors.indexOf(r2Settings.getItem(r2Settings.keys.THEME)), function(theme) {
|
||||
r2Settings.setItem(r2Settings.keys.THEME, theme);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
drawReset(dom) {
|
||||
var _this = this;
|
||||
uiActionButton(dom, function() {
|
||||
r2Settings.resetAll();
|
||||
// TODO, tmp (+ currently, would update only focused panel -> settings)
|
||||
uiContext.widgetContainer.updateManagers.updates.apply();
|
||||
_this.draw();
|
||||
componentHandler.upgradeDom();
|
||||
}, 'RESET');
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {OverviewWidget} from './OverviewWidget';
|
||||
import {HexdumpWidget} from './HexdumpWidget';
|
||||
import {DebuggerWidget} from './DebuggerWidget';
|
||||
import {FunctionsWidget} from './FunctionsWidget';
|
||||
import {ClassesWidget} from './ClassesWidget';
|
||||
import {FlagsWidget} from './FlagsWidget';
|
||||
import {FlagsSpacesWidget} from './FlagsSpacesWidget';
|
||||
import {SearchWidget} from './SearchWidget';
|
||||
@@ -73,6 +74,9 @@ export class WidgetFactory {
|
||||
case Widgets.FUNCTIONS:
|
||||
this.widgets[widget] = new FunctionsWidget();
|
||||
break;
|
||||
case Widgets.CLASSES:
|
||||
this.widgets[widget] = new ClassesWidget();
|
||||
break;
|
||||
case Widgets.FLAGS:
|
||||
this.widgets[widget] = new FlagsWidget();
|
||||
break;
|
||||
|
||||
Reference in new issue
Block a user