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.
![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"
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()"
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"
@@ -76,6 +76,16 @@
</svg>
Parse Memory
</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()"
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"
@@ -134,11 +144,41 @@
</label>
</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 -->
<div class="overflow-x-auto custom-scrollbar">
<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">
<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"
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')">
@@ -181,7 +221,7 @@
class="bg-white dark:bg-slate-800 divide-y divide-slate-200 dark:divide-slate-700">
<!-- Content injected by JS -->
<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>
</tbody>
</table>

147
src.js
View File

@@ -1,5 +1,6 @@
let allRegions = [];
let currentSort = { column: null, direction: "asc" };
let selectedRegions = new Set();
// Dark Mode Toggle
function toggleDarkMode() {
@@ -95,6 +96,7 @@ function parseMemory() {
}
allRegions = regions;
selectedRegions.clear();
updateStats(regions);
document.getElementById("stats").classList.remove("hidden");
document.getElementById("stats").classList.add("grid");
@@ -189,7 +191,10 @@ function renderTable(regions) {
: "hover:bg-slate-50 dark:hover:bg-slate-700";
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.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>
@@ -214,6 +219,9 @@ function renderTable(regions) {
remaining > 0
? `${dataset.length} rows shown (${remaining} hidden for performance)`
: `${dataset.length} rows`;
// Update select-all checkbox state
updateSelectAllCheckbox();
}
// --- Helpers & Utilities ---
@@ -249,7 +257,7 @@ function sortBy(column) {
// Update active header
const headers = document.querySelectorAll("th");
headers.forEach((th) => {
if (th.onclick.toString().includes(column)) {
if (th.onclick && th.onclick.toString().includes(column)) {
const icon = th.querySelector(".sort-icon");
icon.textContent = currentSort.direction === "asc" ? "▲" : "▼";
icon.classList.add("text-indigo-600");
@@ -260,6 +268,136 @@ function sortBy(column) {
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() {
const addrFilter = document
.getElementById("addressFilter")
@@ -343,11 +481,12 @@ function applyFilters() {
function clearAll() {
document.getElementById("input").value = "";
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.remove("grid");
document.getElementById("filter-section").classList.add("hidden");
document.getElementById("filter-section").classList.remove("flex");
document.getElementById("command-output").classList.add("hidden");
document.getElementById("footer-count").innerText = "0 rows";
// Reset filters
@@ -357,4 +496,6 @@ function clearAll() {
allRegions = [];
currentSort = { column: null, direction: "asc" };
selectedRegions.clear();
updateGenerateButton();
}