mirror of
https://github.com/DarrylNixon/binhop
synced 2024-04-22 12:37:06 -07:00
suuuper alpha. might fall over. v0.0.1.
This commit is contained in:
parent
02e660538f
commit
bc4f021fa9
6 changed files with 463 additions and 118 deletions
109
static/binhop.css
Normal file
109
static/binhop.css
Normal file
|
@ -0,0 +1,109 @@
|
|||
body {
|
||||
color: #fff;
|
||||
}
|
||||
.header {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.logo {
|
||||
margin-top: 50px;
|
||||
margin-bottom: 30px;
|
||||
width: 200px;
|
||||
}
|
||||
.button-center {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
button {
|
||||
background-color: #3498db;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
#footer {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
background-color:#263745;
|
||||
padding: .2em;
|
||||
opacity: 0.4;
|
||||
}
|
||||
/* Yet unused, from https://iamkate.com/code/tree-views/ */
|
||||
/* .tree{
|
||||
--spacing : 1.5rem;
|
||||
--radius : 10px;
|
||||
}
|
||||
.tree li{
|
||||
display : block;
|
||||
position : relative;
|
||||
padding-left : calc(2 * var(--spacing) - var(--radius) - 2px);
|
||||
}
|
||||
.tree ul{
|
||||
margin-left : calc(var(--radius) - var(--spacing));
|
||||
padding-left : 0;
|
||||
}
|
||||
.tree ul li{
|
||||
border-left : 2px solid #ddd;
|
||||
}
|
||||
.tree ul li:last-child{
|
||||
border-color : transparent;
|
||||
}
|
||||
.tree ul li::before{
|
||||
content : '';
|
||||
display : block;
|
||||
position : absolute;
|
||||
top : calc(var(--spacing) / -2);
|
||||
left : -2px;
|
||||
width : calc(var(--spacing) + 2px);
|
||||
height : calc(var(--spacing) + 1px);
|
||||
border : solid #ddd;
|
||||
border-width : 0 0 2px 2px;
|
||||
}
|
||||
.tree summary{
|
||||
display : block;
|
||||
cursor : pointer;
|
||||
}
|
||||
.tree summary::marker, .tree summary::-webkit-details-marker{
|
||||
display : none;
|
||||
}
|
||||
.tree summary:focus{
|
||||
outline : none;
|
||||
}
|
||||
.tree summary:focus-visible{
|
||||
outline : 1px dotted #000;
|
||||
}
|
||||
.tree li::after, .tree summary::before{
|
||||
content : '';
|
||||
display : block;
|
||||
position : absolute;
|
||||
top : calc(var(--spacing) / 2 - var(--radius));
|
||||
left : calc(var(--spacing) - var(--radius) - 1px);
|
||||
width : calc(2 * var(--radius));
|
||||
height : calc(2 * var(--radius));
|
||||
border-radius : 50%;
|
||||
background : #ddd;
|
||||
}
|
||||
.tree summary::before{
|
||||
content : '+';
|
||||
z-index : 1;
|
||||
background : #696;
|
||||
color : #fff;
|
||||
line-height : calc(2 * var(--radius) - 2px);
|
||||
text-align : center;
|
||||
}
|
||||
.tree details[open] > summary::before{
|
||||
content : '−';
|
||||
} */
|
273
static/binhop.js
273
static/binhop.js
|
@ -1,40 +1,197 @@
|
|||
function draw_bytes(data) {
|
||||
const canvas = document.getElementById("blob");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const colorScale = chroma.scale(['#4c6c81', '#9c2f2f']).mode('lch');
|
||||
|
||||
// canvas.width = canvas.parentNode.width;
|
||||
// canvas.height = canvas.parentNode.height;
|
||||
|
||||
const blockSize = 2;
|
||||
const blockPadding = 1;
|
||||
const numBlocks = Math.ceil(data.meta.sizeb / 1200);
|
||||
const blockSize = canvas.width / numBlocks;
|
||||
|
||||
ctx.fillStyle = "#ddd";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const numBlocks = Math.ceil(data.meta.sizeb / (blockSize + blockPadding));
|
||||
for (let i = 0; i < numBlocks; i++) {
|
||||
const x = i * (blockSize + blockPadding);
|
||||
ctx.fillRect(x, 0, blockSize, canvas.height);
|
||||
}
|
||||
// console.log("size: " + data.meta.sizeb);
|
||||
// console.log("numblocks: " + numBlocks);
|
||||
data.offsets.forEach((offset, idx) => {
|
||||
const start = Math.floor((offset.start / data.meta.sizeb) * numBlocks);
|
||||
const end = Math.ceil((offset.end / data.meta.sizeb) * numBlocks);
|
||||
// console.log(offset.d + ": " + start + ", " + end + " | " + offset.start + ", " + offset.end);
|
||||
ctx.fillStyle = colorScale(idx / data.offsets.length).alpha(0.2);
|
||||
ctx.fillRect(start * blockSize, 0, (end - start) * blockSize, canvas.height);
|
||||
});
|
||||
|
||||
ctx.fillStyle = "blue";
|
||||
data.offsets.forEach((offset) => {
|
||||
const start = Math.floor(offset.start / (blockSize + blockPadding));
|
||||
const end = Math.ceil(offset.end / (blockSize + blockPadding));
|
||||
for (let i = start; i < end; i++) {
|
||||
const x = i * (blockSize + blockPadding);
|
||||
ctx.fillRect(x, 0, blockSize, canvas.height);
|
||||
}
|
||||
canvas.addEventListener('mousemove', function(event) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const mouseX = event.clientX - rect.left;
|
||||
const mouseY = event.clientY - rect.top;
|
||||
|
||||
const tooltips = [];
|
||||
|
||||
data.offsets.forEach((offset, idx) => {
|
||||
const start = Math.floor((offset.start / data.meta.sizeb) * numBlocks);
|
||||
const end = Math.ceil((offset.end / data.meta.sizeb) * numBlocks);
|
||||
const rectX = start * blockSize;
|
||||
const rectY = 0;
|
||||
const rectWidth = (end - start) * blockSize;
|
||||
const rectHeight = canvas.height;
|
||||
|
||||
if (mouseX >= rectX && mouseX <= rectX + rectWidth && mouseY >= rectY && mouseY <= rectY + rectHeight) {
|
||||
const tooltipText = ` ${first_substr(offset.d)}`;
|
||||
const tooltipWidth = ctx.measureText(tooltipText).width + 20;
|
||||
const tooltipHeight = 30;
|
||||
|
||||
let tooltipX = mouseX + 10;
|
||||
let tooltipY = mouseY - tooltipHeight - 10;
|
||||
|
||||
if (tooltipX + tooltipWidth > canvas.width) {
|
||||
tooltipX = canvas.width - tooltipWidth;
|
||||
}
|
||||
|
||||
if (tooltipY < 0) {
|
||||
tooltipY = 0;
|
||||
}
|
||||
|
||||
// Check for overlapping tooltips
|
||||
for (let i = 0; i < tooltips.length; i++) {
|
||||
const otherTooltip = tooltips[i];
|
||||
|
||||
if (tooltipX < otherTooltip.x + otherTooltip.width &&
|
||||
tooltipX + tooltipWidth > otherTooltip.x &&
|
||||
tooltipY < otherTooltip.y + otherTooltip.height &&
|
||||
tooltipY + tooltipHeight > otherTooltip.y) {
|
||||
|
||||
// Overlapping, adjust the position
|
||||
tooltipX = otherTooltip.x + otherTooltip.width + 10;
|
||||
|
||||
if (tooltipX + tooltipWidth > canvas.width) {
|
||||
tooltipX = mouseX - tooltipWidth - 10;
|
||||
}
|
||||
|
||||
if (tooltipX < 0) {
|
||||
tooltipX = 0;
|
||||
}
|
||||
|
||||
tooltipY = otherTooltip.y;
|
||||
}
|
||||
}
|
||||
|
||||
tooltips.push({
|
||||
x: tooltipX,
|
||||
y: tooltipY,
|
||||
width: tooltipWidth,
|
||||
height: tooltipHeight
|
||||
});
|
||||
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.fillRect(tooltipX, tooltipY, tooltipWidth, tooltipHeight);
|
||||
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.font = '.6em Arial';
|
||||
ctx.fillText(tooltipText, tooltipX + 10, tooltipY + 20);
|
||||
// colored circle
|
||||
const circleSize = 10;
|
||||
ctx.fillStyle = colorScale(idx / data.offsets.length).alpha(0.2);
|
||||
ctx.beginPath();
|
||||
ctx.arc(tooltipX + 15, tooltipY + 15, circleSize / 2, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
// re-draw the rest
|
||||
ctx.fillStyle = colorScale(idx / data.offsets.length).alpha(0.2);
|
||||
ctx.fillRect(start * blockSize, 0, (end - start) * blockSize, canvas.height);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseout', function(event) {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
data.offsets.forEach((offset, idx) => {
|
||||
const start = Math.floor((offset.start / data.meta.sizeb) * numBlocks);
|
||||
const end = Math.ceil((offset.end / data.meta.sizeb) * numBlocks);
|
||||
ctx.fillStyle = colorScale(idx / data.offsets.length).alpha(0.2);
|
||||
ctx.fillRect(start * blockSize, 0, (end - start) * blockSize, canvas.height);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// function create_listing(listing) {
|
||||
// const ul = document.createElement('ul');
|
||||
// ul.classList.add("tree-padding", "tree-vertical-lines", "tree-horizontal-lines", "tree-summaries", "tree-markers", "tree-buttons")
|
||||
|
||||
// for (const key in listing) {
|
||||
// const value = listing[key];
|
||||
|
||||
// if (typeof value == 'object') {
|
||||
// const li = document.createElement('li');
|
||||
// const summary = document.createElement('summary');
|
||||
// summary.innerText = key;
|
||||
// li.appendChild(summary);
|
||||
|
||||
// const nestedUl = create_listing(value);
|
||||
// li.appendChild(nestedUl);
|
||||
// ul.appendChild(li);
|
||||
// } else {
|
||||
// const li = document.createElement('li');
|
||||
// const summary = document.createElement('summary');
|
||||
// summary.classList.add('no-pico');
|
||||
// summary.innerText = `${key} (${value.s})`;
|
||||
// li.appendChild(summary);
|
||||
// ul.appendChild(li);
|
||||
// }
|
||||
// }
|
||||
// return ul;
|
||||
// }
|
||||
|
||||
function file_summaries(data) {
|
||||
const ul = document.createElement('ul');
|
||||
for (const [key, val] of Object.entries(data.ql)) {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = `${val}`;
|
||||
ul.appendChild(li);
|
||||
}
|
||||
return ul;
|
||||
}
|
||||
|
||||
function create_listing(listing) {
|
||||
const ul = document.createElement('ul');
|
||||
|
||||
for (const key in listing) {
|
||||
const value = listing[key];
|
||||
|
||||
if ('s' in value) {
|
||||
const li = document.createElement('li');
|
||||
li.innerText = `${key} (${bytes_to_human(value.s)})`;
|
||||
ul.appendChild(li);
|
||||
}
|
||||
else { // (typeof value === 'object') {
|
||||
const li = document.createElement('li');
|
||||
|
||||
li.innerText = `${key}/`;
|
||||
|
||||
const nestedUl = create_listing(value);
|
||||
|
||||
li.appendChild(nestedUl);
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
return ul;
|
||||
}
|
||||
|
||||
function bytes_to_human(bytes) {
|
||||
num = parseInt(bytes);
|
||||
const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
let i =0;
|
||||
while (bytes >= 1024 && i < units.length - 1) {
|
||||
bytes /= 1024;
|
||||
while (num >= 1024 && i < units.length - 1) {
|
||||
num /= 1024;
|
||||
i++;
|
||||
}
|
||||
return bytes.toFixed(2) + " " + units[i];
|
||||
return num.toFixed(2) + " " + units[i];
|
||||
}
|
||||
|
||||
function first_substr(description) {
|
||||
const index = description.indexOf(',');
|
||||
if (index === -1) {
|
||||
return description;
|
||||
} else {
|
||||
return description.substring(0, index);
|
||||
}
|
||||
}
|
||||
|
||||
async function process_upload(file) {
|
||||
|
@ -52,24 +209,26 @@ async function process_upload(file) {
|
|||
});
|
||||
|
||||
if (response.ok) {
|
||||
// if (1 === 1) {
|
||||
const data = await response.json();
|
||||
// const json = '{"meta":{"name":"miwifi_r4av2_firmware_6bdd4_2.30.500.bin","sizeb":13632488,"sha1":"6cefd9d5de3b80799d0341b19043d108624dcaca","md5":"639616a5b70983903ccdd019a7a6bdd4","sig_quant":411,"duration":"00:05.457"},"offsets":[{"start":960,"end":13630770,"d":"LZMA compressed data"},{"start":1704660,"end":13621866,"d":"Squashfs filesystem"},{"start":4534008,"end":5267984,"d":"xz compressed data"},{"start":5258380,"end":5267984,"d":"ASCII cpio archive"}]}';
|
||||
// const data = JSON.parse(json);
|
||||
|
||||
const container = document.querySelector('.container');
|
||||
|
||||
// visualization
|
||||
container.innerHTML = `
|
||||
<div class="grid">
|
||||
<div id="blob-column">
|
||||
<canvas id="blob" width="600px" height="600px" style="background-color: #18232c;"></canvas>
|
||||
<div id="blob-row">
|
||||
<canvas id="blob" width="1200px" height="60px" style="background-color: #18232c;"></canvas>
|
||||
</div>
|
||||
<div id="info-column">
|
||||
<small>
|
||||
<h1>${data.meta.name}</h1>
|
||||
<div id="info-row">
|
||||
<h4 style="margin-bottom:0.5em;">${data.meta.name}</h4>
|
||||
<details open>
|
||||
<summary>blob meta</summary>
|
||||
<figure>
|
||||
<table role="grid">
|
||||
<tr>
|
||||
<th><strong>filename:</strong></th>
|
||||
<th><strong>input filename:</strong></th>
|
||||
<td>${data.meta.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -77,35 +236,59 @@ async function process_upload(file) {
|
|||
<td>${data.meta.duration}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>size:</strong></th>
|
||||
<td>${data.meta["sizeb"]} (${bytes_to_human(data.meta.sizeb)})</td>
|
||||
<th><strong>file size in bytes:</strong></th>
|
||||
<td>${data.meta.sizeb} (${bytes_to_human(data.meta.sizeb)})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>sha1:</strong></th>
|
||||
<th><strong>files discovered:</strong></th>
|
||||
<td>${data.meta.files}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>directories unpacked:</strong></th>
|
||||
<td>${data.meta.dirs}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>signatures tried:</strong></th>
|
||||
<td>${data.meta.sig_quant}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>sha1 hash:</strong></th>
|
||||
<td>${data.meta.sha1}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><strong>md5:</strong></th>
|
||||
<th><strong>md5 hash:</strong></th>
|
||||
<td>${data.meta.md5}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</figure>
|
||||
</details>
|
||||
<article>
|
||||
<header>Header</header>
|
||||
Body
|
||||
<footer>Footer</footer>
|
||||
</article>
|
||||
<article>
|
||||
<header>Header</header>
|
||||
Body
|
||||
<footer>Footer</footer>
|
||||
</article>
|
||||
<pre>${JSON.stringify(data, null, 2)}</pre>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<details open>
|
||||
<summary>binwalk extraction log</summary>
|
||||
<small>
|
||||
<div id="quick-look"></div>
|
||||
</small>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>full file listing</summary>
|
||||
<small>
|
||||
<div id="file-list"></div>
|
||||
</small>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>raw json response (dev)</summary>
|
||||
<pre>${JSON.stringify(data, null, 2)}</pre>
|
||||
</details>
|
||||
</div>`;
|
||||
draw_bytes(data);
|
||||
|
||||
const file_list = document.getElementById('file-list');
|
||||
file_list.appendChild(create_listing(data.ls[Object.keys(data.ls)[0]]));
|
||||
|
||||
const quick_look = document.getElementById('quick-look');
|
||||
quick_look.appendChild(file_summaries(data));
|
||||
} else {
|
||||
console.error('error uploading file');
|
||||
}
|
||||
|
|
58
static/chroma.min.js
vendored
Normal file
58
static/chroma.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue