Add region selection and command generation features

This commit is contained in:
√(noham)²
2026-01-25 12:05:11 +01:00
parent ad9a07dafb
commit 3f54439ad8
4 changed files with 188 additions and 6 deletions

View File

@@ -25,4 +25,5 @@ Dirty pages: 0x100004000, 0x100005000
Then paste it into the input area of this tool to visualize the memory regions. Then paste it into the input area of this tool to visualize the memory regions.
![docs/images/screen.png](docs/images/screen.png) ![docs/images/screen.png](docs/images/screen.png)
![docs/images/screen2.png](docs/images/screen2.png)

BIN
docs/images/screen2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

View File

@@ -64,7 +64,7 @@
class="w-full h-48 p-4 font-mono text-xs bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all resize-y text-slate-800 dark:text-slate-200" class="w-full h-48 p-4 font-mono text-xs bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all resize-y text-slate-800 dark:text-slate-200"
placeholder="[0x0000000100000000-0x0000000100004000) r-x __TEXT..."></textarea> placeholder="[0x0000000100000000-0x0000000100004000) r-x __TEXT..."></textarea>
<div class="mt-4 flex gap-3"> <div class="mt-4 flex gap-3 flex-wrap">
<button onclick="parseMemory()" <button onclick="parseMemory()"
class="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2.5 rounded-lg font-medium transition shadow-sm active:transform active:scale-95"> class="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2.5 rounded-lg font-medium transition shadow-sm active:transform active:scale-95">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
@@ -76,6 +76,16 @@
</svg> </svg>
Parse Memory Parse Memory
</button> </button>
<button onclick="generateCommands()" id="generate-btn"
class="flex items-center gap-2 bg-emerald-600 hover:bg-emerald-700 text-white px-6 py-2.5 rounded-lg font-medium transition shadow-sm active:transform active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed"
disabled>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
Generate Commands
</button>
<button onclick="clearAll()" <button onclick="clearAll()"
class="flex items-center gap-2 bg-white dark:bg-slate-700 border border-slate-300 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-200 px-6 py-2.5 rounded-lg font-medium transition shadow-sm"> class="flex items-center gap-2 bg-white dark:bg-slate-700 border border-slate-300 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-200 px-6 py-2.5 rounded-lg font-medium transition shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
@@ -134,11 +144,41 @@
</label> </label>
</div> </div>
<!-- Command Output Section -->
<div id="command-output"
class="hidden p-6 bg-slate-50 dark:bg-slate-800/50 border-b border-slate-100 dark:border-slate-700">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-semibold text-slate-700 dark:text-slate-300 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-indigo-500" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
Generated Commands (<span id="selected-count">0</span> selected)
</h3>
<button onclick="copyCommands(event)"
class="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg font-medium transition shadow-sm text-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
Copy All
</button>
</div>
<pre id="commands-text"
class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-600 rounded-lg p-4 text-xs font-mono text-slate-800 dark:text-slate-200 overflow-x-auto custom-scrollbar max-h-64"></pre>
</div>
<!-- Table --> <!-- Table -->
<div class="overflow-x-auto custom-scrollbar"> <div class="overflow-x-auto custom-scrollbar">
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-700"> <table class="min-w-full divide-y divide-slate-200 dark:divide-slate-700">
<thead class="bg-slate-50 dark:bg-slate-800 sticky top-0 z-10"> <thead class="bg-slate-50 dark:bg-slate-800 sticky top-0 z-10">
<tr> <tr>
<th scope="col" class="px-6 py-3 text-left">
<input type="checkbox" id="select-all" onchange="toggleSelectAll()"
class="rounded text-indigo-600 focus:ring-indigo-500 cursor-pointer">
</th>
<th scope="col" <th scope="col"
class="px-6 py-3 text-left text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-700 group" class="px-6 py-3 text-left text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-700 group"
onclick="sortBy('start')"> onclick="sortBy('start')">
@@ -181,7 +221,7 @@
class="bg-white dark:bg-slate-800 divide-y divide-slate-200 dark:divide-slate-700"> class="bg-white dark:bg-slate-800 divide-y divide-slate-200 dark:divide-slate-700">
<!-- Content injected by JS --> <!-- Content injected by JS -->
<tr class="text-center text-slate-400"> <tr class="text-center text-slate-400">
<td colspan="6" class="py-12">Waiting for input...</td> <td colspan="7" class="py-12">Waiting for input...</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

147
src.js
View File

@@ -1,5 +1,6 @@
let allRegions = []; let allRegions = [];
let currentSort = { column: null, direction: "asc" }; let currentSort = { column: null, direction: "asc" };
let selectedRegions = new Set();
// Dark Mode Toggle // Dark Mode Toggle
function toggleDarkMode() { function toggleDarkMode() {
@@ -95,6 +96,7 @@ function parseMemory() {
} }
allRegions = regions; allRegions = regions;
selectedRegions.clear();
updateStats(regions); updateStats(regions);
document.getElementById("stats").classList.remove("hidden"); document.getElementById("stats").classList.remove("hidden");
document.getElementById("stats").classList.add("grid"); document.getElementById("stats").classList.add("grid");
@@ -189,7 +191,10 @@ function renderTable(regions) {
: "hover:bg-slate-50 dark:hover:bg-slate-700"; : "hover:bg-slate-50 dark:hover:bg-slate-700";
return ` return `
<tr class="${rowClass} transition-colors border-b border-slate-100 dark:border-slate-700 last:border-0"> <tr class="${rowClass} transition-colors border-b border-slate-100 dark:border-slate-700 last:border-0" data-start="${r.startStr}" data-end="${r.endStr}">
<td class="px-6 py-3 whitespace-nowrap">
<input type="checkbox" class="row-checkbox rounded text-indigo-600 focus:ring-indigo-500 cursor-pointer" data-start="${r.startStr}" data-end="${r.endStr}" onchange="updateSelection()" ${selectedRegions.has(r.startStr) ? "checked" : ""}>
</td>
<td class="px-6 py-3 whitespace-nowrap font-mono text-xs text-slate-600 dark:text-slate-400 select-all">${r.startStr}</td> <td class="px-6 py-3 whitespace-nowrap font-mono text-xs text-slate-600 dark:text-slate-400 select-all">${r.startStr}</td>
<td class="px-6 py-3 whitespace-nowrap font-mono text-xs text-slate-600 dark:text-slate-400 select-all">${r.endStr}</td> <td class="px-6 py-3 whitespace-nowrap font-mono text-xs text-slate-600 dark:text-slate-400 select-all">${r.endStr}</td>
<td class="px-6 py-3 whitespace-nowrap text-sm text-slate-700 dark:text-slate-300 font-medium" title="${r.size.toString()} bytes">${formatBytes(r.size)}</td> <td class="px-6 py-3 whitespace-nowrap text-sm text-slate-700 dark:text-slate-300 font-medium" title="${r.size.toString()} bytes">${formatBytes(r.size)}</td>
@@ -214,6 +219,9 @@ function renderTable(regions) {
remaining > 0 remaining > 0
? `${dataset.length} rows shown (${remaining} hidden for performance)` ? `${dataset.length} rows shown (${remaining} hidden for performance)`
: `${dataset.length} rows`; : `${dataset.length} rows`;
// Update select-all checkbox state
updateSelectAllCheckbox();
} }
// --- Helpers & Utilities --- // --- Helpers & Utilities ---
@@ -249,7 +257,7 @@ function sortBy(column) {
// Update active header // Update active header
const headers = document.querySelectorAll("th"); const headers = document.querySelectorAll("th");
headers.forEach((th) => { headers.forEach((th) => {
if (th.onclick.toString().includes(column)) { if (th.onclick && th.onclick.toString().includes(column)) {
const icon = th.querySelector(".sort-icon"); const icon = th.querySelector(".sort-icon");
icon.textContent = currentSort.direction === "asc" ? "▲" : "▼"; icon.textContent = currentSort.direction === "asc" ? "▲" : "▼";
icon.classList.add("text-indigo-600"); icon.classList.add("text-indigo-600");
@@ -260,6 +268,136 @@ function sortBy(column) {
applyFilters(); applyFilters();
} }
// --- Selection and Command Generation ---
function toggleSelectAll() {
const selectAllCheckbox = document.getElementById("select-all");
const checkboxes = document.querySelectorAll(".row-checkbox");
checkboxes.forEach((checkbox) => {
checkbox.checked = selectAllCheckbox.checked;
const start = checkbox.dataset.start;
const end = checkbox.dataset.end;
if (selectAllCheckbox.checked) {
selectedRegions.add(start);
} else {
selectedRegions.delete(start);
}
});
updateGenerateButton();
updateSelectedCount();
}
function updateSelection() {
const checkboxes = document.querySelectorAll(".row-checkbox");
checkboxes.forEach((checkbox) => {
const start = checkbox.dataset.start;
if (checkbox.checked) {
selectedRegions.add(start);
} else {
selectedRegions.delete(start);
}
});
updateSelectAllCheckbox();
updateGenerateButton();
updateSelectedCount();
}
function updateSelectAllCheckbox() {
const selectAllCheckbox = document.getElementById("select-all");
const checkboxes = document.querySelectorAll(".row-checkbox");
if (checkboxes.length === 0) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
return;
}
const checkedCount = Array.from(checkboxes).filter((cb) => cb.checked).length;
if (checkedCount === 0) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
} else if (checkedCount === checkboxes.length) {
selectAllCheckbox.checked = true;
selectAllCheckbox.indeterminate = false;
} else {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = true;
}
}
function updateGenerateButton() {
const generateBtn = document.getElementById("generate-btn");
generateBtn.disabled = selectedRegions.size === 0;
}
function updateSelectedCount() {
const countSpan = document.getElementById("selected-count");
if (countSpan) {
countSpan.textContent = selectedRegions.size;
}
}
function generateCommands() {
if (selectedRegions.size === 0) return;
const commands = [];
// Get selected regions in order
const selectedRegionObjs = allRegions.filter((r) =>
selectedRegions.has(r.startStr),
);
selectedRegionObjs.forEach((region) => {
const startAddr = region.startStr;
const endAddr = region.endStr;
const command = `memory read --outfile /tmp/dump_${startAddr}_${endAddr}.bin --binary ${startAddr} ${endAddr}`;
commands.push(command);
});
const commandsText = document.getElementById("commands-text");
commandsText.textContent = commands.join("\n");
document.getElementById("command-output").classList.remove("hidden");
// Scroll to commands
document
.getElementById("command-output")
.scrollIntoView({ behavior: "smooth", block: "nearest" });
}
function copyCommands(event) {
const commandsText = document.getElementById("commands-text").textContent;
navigator.clipboard
.writeText(commandsText)
.then(() => {
// Visual feedback
const copyBtn = event.target.closest("button");
const originalText = copyBtn.innerHTML;
copyBtn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
Copied!
`;
setTimeout(() => {
copyBtn.innerHTML = originalText;
}, 2000);
})
.catch((err) => {
console.error("Failed to copy:", err);
alert("Failed to copy to clipboard");
});
}
function applyFilters() { function applyFilters() {
const addrFilter = document const addrFilter = document
.getElementById("addressFilter") .getElementById("addressFilter")
@@ -343,11 +481,12 @@ function applyFilters() {
function clearAll() { function clearAll() {
document.getElementById("input").value = ""; document.getElementById("input").value = "";
document.getElementById("table-body").innerHTML = document.getElementById("table-body").innerHTML =
`<tr><td colspan="6" class="py-12 text-center text-slate-400">Waiting for input...</td></tr>`; `<tr><td colspan="7" class="py-12 text-center text-slate-400">Waiting for input...</td></tr>`;
document.getElementById("stats").classList.add("hidden"); document.getElementById("stats").classList.add("hidden");
document.getElementById("stats").classList.remove("grid"); document.getElementById("stats").classList.remove("grid");
document.getElementById("filter-section").classList.add("hidden"); document.getElementById("filter-section").classList.add("hidden");
document.getElementById("filter-section").classList.remove("flex"); document.getElementById("filter-section").classList.remove("flex");
document.getElementById("command-output").classList.add("hidden");
document.getElementById("footer-count").innerText = "0 rows"; document.getElementById("footer-count").innerText = "0 rows";
// Reset filters // Reset filters
@@ -357,4 +496,6 @@ function clearAll() {
allRegions = []; allRegions = [];
currentSort = { column: null, direction: "asc" }; currentSort = { column: null, direction: "asc" };
selectedRegions.clear();
updateGenerateButton();
} }