function draw_bytes(data) { const canvas = document.getElementById("blob"); const ctx = canvas.getContext("2d"); const colorScale = chroma.scale(['#4c6c81', '#9c2f2f']).mode('lch'); const numBlocks = Math.ceil(data.meta.sizeb / 1200); const blockSize = canvas.width / numBlocks; ctx.fillStyle = "#ddd"; ctx.fillRect(0, 0, canvas.width, 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); }); 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 (num >= 1024 && i < units.length - 1) { num /= 1024; 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) { const formData = new FormData(); formData.append('file', file); const button = document.querySelector('button'); button.setAttribute('aria-busy', 'true'); button.textContent = `processing ${file.name}...`; const response = await fetch('api/upload_file', { method: 'POST', body: formData, timeout: 600000 // 10 minutes }); 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 = `
input filename: | ${data.meta.name} |
---|---|
process duration: | ${data.meta.duration} |
file size in bytes: | ${data.meta.sizeb} (${bytes_to_human(data.meta.sizeb)}) |
files discovered: | ${data.meta.files} |
directories unpacked: | ${data.meta.dirs} |
signatures tried: | ${data.meta.sig_quant} |
sha1 hash: | ${data.meta.sha1} |
md5 hash: | ${data.meta.md5} |
${JSON.stringify(data, null, 2)}