let allRegions = []; let currentSort = { column: null, direction: "asc" }; // 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; 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`; } // --- 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.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(); } 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("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" }; }