let allRegions = []; let currentSort = { column: null, direction: "asc" }; let selectedRegions = new Set(); // Dark Mode Toggle function toggleDarkMode() { const html = document.documentElement; const sunIcon = document.getElementById("theme-icon-sun"); const moonIcon = document.getElementById("theme-icon-moon"); if (html.classList.contains("dark")) { html.classList.remove("dark"); html.classList.add("light"); sunIcon.classList.add("hidden"); moonIcon.classList.remove("hidden"); localStorage.setItem("theme", "light"); } else { html.classList.remove("light"); html.classList.add("dark"); sunIcon.classList.remove("hidden"); moonIcon.classList.add("hidden"); localStorage.setItem("theme", "dark"); } } // Initialize theme from localStorage (function initTheme() { const savedTheme = localStorage.getItem("theme") || "light"; const html = document.documentElement; const sunIcon = document.getElementById("theme-icon-sun"); const moonIcon = document.getElementById("theme-icon-moon"); if (savedTheme === "dark") { html.classList.remove("light"); html.classList.add("dark"); if (sunIcon) sunIcon.classList.remove("hidden"); if (moonIcon) moonIcon.classList.add("hidden"); } else { html.classList.remove("dark"); html.classList.add("light"); if (sunIcon) sunIcon.classList.add("hidden"); if (moonIcon) moonIcon.classList.remove("hidden"); } })(); // Parse Logic function parseMemory() { const input = document.getElementById("input").value; if (!input.trim()) return; const lines = input.split("\n"); const regions = []; let currentRegion = null; // Updated Regex to be more robust // Captures: 1: Start, 2: End, 3: Perms, 4: Name (optional) const regionRegex = /\[(0x[0-9a-fA-F]+)-(0x[0-9a-fA-F]+)\)\s+([r\-][w\-][x\-][s\-]?)(?:\s+(.*))?/i; const dirtyRegex = /Dirty pages:\s+(.+)/i; for (let line of lines) { line = line.trim(); if (!line) continue; const regionMatch = line.match(regionRegex); if (regionMatch) { const start = regionMatch[1]; const end = regionMatch[2]; const perms = regionMatch[3]; const name = regionMatch[4] || ""; currentRegion = { startStr: start, // Keep string for display endStr: end, // Keep string for display startBig: BigInt(start), // Use BigInt for calculation endBig: BigInt(end), // Use BigInt for calculation perms, name, dirtyPages: [], dirtyCount: 0, }; // Calculate size safely using BigInt currentRegion.size = currentRegion.endBig - currentRegion.startBig; regions.push(currentRegion); } else if (currentRegion) { // Check for metadata lines associated with the previous region const dirtyMatch = line.match(dirtyRegex); if (dirtyMatch) { const pages = dirtyMatch[1].split(",").map((p) => p.trim()); currentRegion.dirtyPages = pages; currentRegion.dirtyCount = pages.length; } } } allRegions = regions; selectedRegions.clear(); updateStats(regions); document.getElementById("stats").classList.remove("hidden"); document.getElementById("stats").classList.add("grid"); document.getElementById("filter-section").classList.remove("hidden"); document.getElementById("filter-section").classList.add("flex"); // Set default sort to Start Address Ascending sortBy("start"); } function updateStats(regions) { const container = document.getElementById("stats"); const totalRegions = regions.length; const totalDirty = regions.reduce((acc, r) => acc + r.dirtyCount, 0); // BigInt total size summation let totalSizeBytes = 0n; regions.forEach((r) => (totalSizeBytes += r.size)); const execCount = regions.filter((r) => r.perms.includes("x")).length; const writeCount = regions.filter((r) => r.perms.includes("w")).length; const createCard = (label, value, colorClass) => `
${label}
${value}
`; container.innerHTML = createCard( "Total Regions", totalRegions, "text-slate-800 dark:text-slate-200", ) + createCard( "Total Size", formatBytes(totalSizeBytes), "text-indigo-600 dark:text-indigo-400", ) + createCard("Executable", execCount, "text-red-500 dark:text-red-400") + createCard("Writable", writeCount, "text-amber-500 dark:text-amber-400") + createCard( "Dirty Pages", totalDirty, "text-emerald-600 dark:text-emerald-400", ); } function renderTable(regions) { const tbody = document.getElementById("table-body"); const footer = document.getElementById("footer-count"); if (regions.length === 0) { tbody.innerHTML = `No matching regions found.`; footer.textContent = "0 rows"; return; } // Limit rendering for performance if massive (simple virtualization cap) const renderLimit = 2000; const dataset = regions.slice(0, renderLimit); const html = dataset .map((r) => { const isExecutable = r.perms.includes("x"); const isWritable = r.perms.includes("w"); const isReadable = r.perms.includes("r"); // Badges for perms let permBadges = ""; if (isReadable) permBadges += `R`; else permBadges += `-`; if (isWritable) permBadges += `W`; else permBadges += `-`; if (isExecutable) permBadges += `X`; else permBadges += `-`; // Row highlighting const rowClass = r.dirtyCount > 0 ? "bg-orange-50 dark:bg-orange-900/20 hover:bg-orange-100 dark:hover:bg-orange-900/30" : "hover:bg-slate-50 dark:hover:bg-slate-700"; return ` ${r.startStr} ${r.endStr} ${formatBytes(r.size)} ${permBadges} ${r.name || 'unnamed'} ${ r.dirtyCount > 0 ? `${r.dirtyCount}` : '-' } `; }) .join(""); tbody.innerHTML = html; const remaining = regions.length - renderLimit; footer.innerHTML = remaining > 0 ? `${dataset.length} rows shown (${remaining} hidden for performance)` : `${dataset.length} rows`; // Update select-all checkbox state updateSelectAllCheckbox(); } // --- Helpers & Utilities --- function formatBytes(bigIntBytes) { if (bigIntBytes === 0n) return "0 B"; // Convert to Number for display formatting (precision loss at PB scale is acceptable for UI) const bytes = Number(bigIntBytes); const k = 1024; const sizes = ["B", "KB", "MB", "GB", "TB", "PB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; } function sortBy(column) { if (currentSort.column === column) { currentSort.direction = currentSort.direction === "asc" ? "desc" : "asc"; } else { currentSort.column = column; currentSort.direction = "asc"; } // Visual indicator helper document .querySelectorAll(".sort-icon") .forEach((el) => (el.textContent = "⇅")); document .querySelectorAll(".sort-icon") .forEach((el) => el.classList.remove("text-indigo-600")); // Update active header const headers = document.querySelectorAll("th"); headers.forEach((th) => { 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"); icon.classList.remove("invisible"); } }); 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 = ` 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") .value.toLowerCase(); const permsFilter = document.getElementById("permsFilter").value; const dirtyFilter = document.getElementById("dirtyFilter").value; const hideEmpty = document.getElementById("hideEmptyRegions").checked; let filtered = allRegions.filter((r) => { // 1. Empty/Permissions check if (hideEmpty && r.perms.startsWith("---")) return false; // 2. Address/Name Search if (addrFilter) { const searchStr = (r.startStr + r.endStr + r.name).toLowerCase(); if (!searchStr.includes(addrFilter)) return false; } // 3. Permissions Dropdown if (permsFilter) { if ( permsFilter === "rw" && (!r.perms.includes("r") || !r.perms.includes("w")) ) return false; else if ( permsFilter === "rx" && (!r.perms.includes("r") || !r.perms.includes("x")) ) return false; else if (permsFilter.length === 1 && !r.perms.includes(permsFilter)) return false; } // 4. Dirty Pages if (dirtyFilter === "dirty" && r.dirtyCount === 0) return false; if (dirtyFilter === "clean" && r.dirtyCount > 0) return false; return true; }); // Apply Sort filtered.sort((a, b) => { let valA, valB; switch (currentSort.column) { case "start": valA = a.startBig; valB = b.startBig; break; case "end": valA = a.endBig; valB = b.endBig; break; case "size": valA = a.size; valB = b.size; break; case "perms": valA = a.perms; valB = b.perms; break; // string comparison case "name": valA = a.name.toLowerCase(); valB = b.name.toLowerCase(); break; case "dirty": valA = a.dirtyCount; valB = b.dirtyCount; break; } if (valA < valB) return currentSort.direction === "asc" ? -1 : 1; if (valA > valB) return currentSort.direction === "asc" ? 1 : -1; return 0; }); renderTable(filtered); } function clearAll() { document.getElementById("input").value = ""; document.getElementById("table-body").innerHTML = `Waiting for input...`; 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 document.getElementById("addressFilter").value = ""; document.getElementById("permsFilter").value = ""; document.getElementById("dirtyFilter").value = ""; allRegions = []; currentSort = { column: null, direction: "asc" }; selectedRegions.clear(); updateGenerateButton(); }