Created
March 10, 2026 10:17
-
-
Save acaburaz/ed2d061f8408e17123a56089c6f41d39 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>NOR Hex Comparator</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Rajdhani:wght@400;600;700&display=swap'); | |
| :root { | |
| --bg: #0a0c0f; | |
| --panel: #0f1318; | |
| --border: #1e2530; | |
| --accent: #00e5ff; | |
| --accent2: #ff3d6b; | |
| --accent3: #39ff6a; | |
| --text: #c8d8e8; | |
| --muted: #4a5a6a; | |
| --diff: #ff3d6b22; | |
| --diff-border: #ff3d6b; | |
| --same: #39ff6a22; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: 'Rajdhani', sans-serif; | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| background: | |
| radial-gradient(ellipse 80% 40% at 20% 0%, #00e5ff08 0%, transparent 60%), | |
| radial-gradient(ellipse 60% 30% at 80% 100%, #ff3d6b06 0%, transparent 60%); | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| .container { | |
| position: relative; | |
| z-index: 1; | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| padding: 2rem; | |
| } | |
| header { | |
| display: flex; | |
| align-items: center; | |
| gap: 1.5rem; | |
| margin-bottom: 2.5rem; | |
| padding-bottom: 1.5rem; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .logo { | |
| font-family: 'Share Tech Mono', monospace; | |
| font-size: 1.1rem; | |
| color: var(--accent); | |
| letter-spacing: 0.15em; | |
| opacity: 0.8; | |
| } | |
| h1 { | |
| font-size: 2rem; | |
| font-weight: 700; | |
| letter-spacing: 0.1em; | |
| color: #fff; | |
| text-transform: uppercase; | |
| } | |
| h1 span { color: var(--accent); } | |
| .drop-zone-row { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1.5rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .drop-zone { | |
| background: var(--panel); | |
| border: 1px dashed var(--border); | |
| border-radius: 8px; | |
| padding: 2rem; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .drop-zone::before { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| background: linear-gradient(135deg, transparent 40%, #ffffff04); | |
| pointer-events: none; | |
| } | |
| .drop-zone:hover, .drop-zone.dragover { | |
| border-color: var(--accent); | |
| background: #0f1318ee; | |
| box-shadow: 0 0 20px #00e5ff15; | |
| } | |
| .drop-zone.loaded { | |
| border-color: var(--accent3); | |
| border-style: solid; | |
| } | |
| .drop-zone input { display: none; } | |
| .drop-label { | |
| font-family: 'Share Tech Mono', monospace; | |
| font-size: 0.75rem; | |
| color: var(--accent); | |
| letter-spacing: 0.2em; | |
| text-transform: uppercase; | |
| margin-bottom: 0.5rem; | |
| } | |
| .drop-icon { font-size: 2rem; margin-bottom: 0.75rem; opacity: 0.4; } | |
| .drop-text { font-size: 0.9rem; color: var(--muted); } | |
| .file-name { | |
| font-family: 'Share Tech Mono', monospace; | |
| font-size: 0.8rem; | |
| color: var(--accent3); | |
| margin-top: 0.5rem; | |
| word-break: break-all; | |
| } | |
| .btn { | |
| background: transparent; | |
| border: 1px solid var(--accent); | |
| color: var(--accent); | |
| font-family: 'Rajdhani', sans-serif; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| padding: 0.75rem 2.5rem; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| clip-path: polygon(8px 0%, 100% 0%, calc(100% - 8px) 100%, 0% 100%); | |
| } | |
| .btn:hover:not(:disabled) { | |
| background: var(--accent); | |
| color: var(--bg); | |
| box-shadow: 0 0 25px #00e5ff40; | |
| } | |
| .btn:disabled { opacity: 0.3; cursor: not-allowed; } | |
| .stats-bar { | |
| display: grid; | |
| grid-template-columns: repeat(4, 1fr); | |
| gap: 1rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .stat { | |
| background: var(--panel); | |
| border: 1px solid var(--border); | |
| border-radius: 6px; | |
| padding: 1rem 1.25rem; | |
| } | |
| .stat-label { | |
| font-size: 0.7rem; | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| color: var(--muted); | |
| margin-bottom: 0.3rem; | |
| } | |
| .stat-value { | |
| font-family: 'Share Tech Mono', monospace; | |
| font-size: 1.3rem; | |
| color: var(--accent); | |
| } | |
| .stat-value.red { color: var(--accent2); } | |
| .stat-value.green { color: var(--accent3); } | |
| .filter-row { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| margin-bottom: 1rem; | |
| flex-wrap: wrap; | |
| } | |
| .filter-label { | |
| font-size: 0.8rem; | |
| letter-spacing: 0.1em; | |
| color: var(--muted); | |
| text-transform: uppercase; | |
| } | |
| .filter-input { | |
| background: var(--panel); | |
| border: 1px solid var(--border); | |
| color: var(--text); | |
| font-family: 'Share Tech Mono', monospace; | |
| font-size: 0.85rem; | |
| padding: 0.4rem 0.75rem; | |
| border-radius: 4px; | |
| width: 160px; | |
| } | |
| .filter-input:focus { outline: none; border-color: var(--accent); } | |
| .results { | |
| background: var(--panel); | |
| border: 1px solid var(--border); | |
| border-radius: 8px; | |
| overflow: hidden; | |
| } | |
| .results-header { | |
| display: grid; | |
| grid-template-columns: 140px 1fr 1fr 80px; | |
| gap: 0; | |
| background: #0a0c0f; | |
| border-bottom: 1px solid var(--border); | |
| font-size: 0.7rem; | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| color: var(--muted); | |
| } | |
| .results-header div { padding: 0.6rem 1rem; } | |
| .results-body { | |
| max-height: 500px; | |
| overflow-y: auto; | |
| scrollbar-width: thin; | |
| scrollbar-color: var(--border) transparent; | |
| } | |
| .diff-row { | |
| display: grid; | |
| grid-template-columns: 140px 1fr 1fr 80px; | |
| border-bottom: 1px solid #1a2028; | |
| font-family: 'Share Tech Mono', monospace; | |
| font-size: 0.8rem; | |
| transition: background 0.1s; | |
| } | |
| .diff-row:hover { background: #ffffff05; } | |
| .diff-row.changed { background: var(--diff); } | |
| .diff-row div { padding: 0.45rem 1rem; word-break: break-all; } | |
| .offset { color: var(--accent); opacity: 0.8; } | |
| .val-a { color: #ff9f7a; } | |
| .val-b { color: #7affb8; } | |
| .badge { | |
| font-size: 0.65rem; | |
| padding: 0.15rem 0.5rem; | |
| border-radius: 3px; | |
| font-family: 'Rajdhani', sans-serif; | |
| font-weight: 700; | |
| letter-spacing: 0.1em; | |
| display: inline-block; | |
| margin-top: 0.1rem; | |
| } | |
| .badge.diff { background: #ff3d6b33; color: var(--accent2); border: 1px solid #ff3d6b44; } | |
| .badge.same { background: #39ff6a22; color: var(--accent3); border: 1px solid #39ff6a33; } | |
| .no-results { | |
| padding: 3rem; | |
| text-align: center; | |
| color: var(--muted); | |
| font-family: 'Share Tech Mono', monospace; | |
| font-size: 0.85rem; | |
| } | |
| .progress-wrap { | |
| background: var(--border); | |
| border-radius: 2px; | |
| height: 3px; | |
| margin-bottom: 1.5rem; | |
| overflow: hidden; | |
| display: none; | |
| } | |
| .progress-bar { | |
| height: 100%; | |
| background: linear-gradient(90deg, var(--accent), var(--accent3)); | |
| width: 0%; | |
| transition: width 0.1s; | |
| } | |
| .region-tags { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 0.5rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .region-tag { | |
| font-size: 0.7rem; | |
| font-family: 'Share Tech Mono', monospace; | |
| padding: 0.25rem 0.6rem; | |
| border-radius: 3px; | |
| border: 1px solid var(--border); | |
| color: var(--muted); | |
| cursor: pointer; | |
| transition: all 0.15s; | |
| letter-spacing: 0.05em; | |
| } | |
| .region-tag:hover, .region-tag.active { | |
| border-color: var(--accent); | |
| color: var(--accent); | |
| background: #00e5ff10; | |
| } | |
| .export-btn { | |
| background: transparent; | |
| border: 1px solid var(--muted); | |
| color: var(--muted); | |
| font-family: 'Rajdhani', sans-serif; | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| padding: 0.4rem 1rem; | |
| cursor: pointer; | |
| border-radius: 4px; | |
| transition: all 0.2s; | |
| } | |
| .export-btn:hover { border-color: var(--accent3); color: var(--accent3); } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <div class="logo">// NOR_TOOL v1.0</div> | |
| <h1>HEX <span>COMPARE</span></h1> | |
| </header> | |
| <div class="drop-zone-row"> | |
| <div class="drop-zone" id="zone-a" onclick="document.getElementById('file-a').click()"> | |
| <div class="drop-label">File A — Original</div> | |
| <div class="drop-icon">📂</div> | |
| <div class="drop-text">Click or drag & drop</div> | |
| <div class="file-name" id="name-a">No file loaded</div> | |
| <input type="file" id="file-a" accept=".bin,.BIN"> | |
| </div> | |
| <div class="drop-zone" id="zone-b" onclick="document.getElementById('file-b').click()"> | |
| <div class="drop-label">File B — Patched</div> | |
| <div class="drop-icon">📂</div> | |
| <div class="drop-text">Click or drag & drop</div> | |
| <div class="file-name" id="name-b">No file loaded</div> | |
| <input type="file" id="file-b" accept=".bin,.BIN"> | |
| </div> | |
| </div> | |
| <div style="display:flex; gap:1rem; align-items:center; margin-bottom:1.5rem; flex-wrap:wrap;"> | |
| <button class="btn" id="compare-btn" disabled onclick="compare()">⚡ Compare</button> | |
| <button class="export-btn" id="export-btn" style="display:none" onclick="exportCSV()">↓ Export CSV</button> | |
| </div> | |
| <div class="progress-wrap" id="progress-wrap"> | |
| <div class="progress-bar" id="progress-bar"></div> | |
| </div> | |
| <div id="stats-bar" class="stats-bar" style="display:none"> | |
| <div class="stat"> | |
| <div class="stat-label">File Size</div> | |
| <div class="stat-value" id="stat-size">—</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="stat-label">Different Bytes</div> | |
| <div class="stat-value red" id="stat-diff">—</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="stat-label">Identical Bytes</div> | |
| <div class="stat-value green" id="stat-same">—</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="stat-label">Diff Regions</div> | |
| <div class="stat-value" id="stat-regions">—</div> | |
| </div> | |
| </div> | |
| <div class="region-tags" id="region-tags"></div> | |
| <div class="filter-row" id="filter-row" style="display:none"> | |
| <span class="filter-label">Filter offset:</span> | |
| <input class="filter-input" id="filter-from" placeholder="From (hex)" oninput="renderTable()"> | |
| <input class="filter-input" id="filter-to" placeholder="To (hex)" oninput="renderTable()"> | |
| <span class="filter-label" style="margin-left:1rem">Show:</span> | |
| <label style="font-size:0.85rem;cursor:pointer;display:flex;align-items:center;gap:0.4rem"> | |
| <input type="checkbox" id="show-same" onchange="renderTable()"> Identical bytes | |
| </label> | |
| </div> | |
| <div class="results" id="results" style="display:none"> | |
| <div class="results-header"> | |
| <div>Offset</div> | |
| <div>File A</div> | |
| <div>File B</div> | |
| <div>Status</div> | |
| </div> | |
| <div class="results-body" id="results-body"></div> | |
| </div> | |
| </div> | |
| <script> | |
| let bufA = null, bufB = null; | |
| let allDiffs = []; | |
| const PS5_REGIONS = [ | |
| { name: "LAN MAC", start: 0x1C4020, end: 0x1C4026 }, | |
| { name: "MB Serial", start: 0x1C7200, end: 0x1C7210 }, | |
| { name: "Console Serial", start: 0x1C7210, end: 0x1C7221 }, | |
| { name: "Model", start: 0x1C7230, end: 0x1C7240 }, | |
| { name: "PS5GNP", start: 0x1C7330, end: 0x1C7340 }, | |
| { name: "WiFi MAC", start: 0x1C73C0, end: 0x1C73D0 }, | |
| { name: "Crypto Block 1", start: 0x004200, end: 0x078700 }, | |
| { name: "Crypto Block 2", start: 0x082200, end: 0x0F6700 }, | |
| ]; | |
| function loadFile(id, buf_setter, zone_id, name_id) { | |
| const file = document.getElementById(id).files[0]; | |
| if (!file) return; | |
| const reader = new FileReader(); | |
| reader.onload = e => { | |
| buf_setter(new Uint8Array(e.target.result)); | |
| document.getElementById(name_id).textContent = file.name; | |
| document.getElementById(zone_id).classList.add('loaded'); | |
| if (bufA && bufB) document.getElementById('compare-btn').disabled = false; | |
| }; | |
| reader.readAsArrayBuffer(file); | |
| } | |
| document.getElementById('file-a').addEventListener('change', () => { | |
| loadFile('file-a', b => bufA = b, 'zone-a', 'name-a'); | |
| }); | |
| document.getElementById('file-b').addEventListener('change', () => { | |
| loadFile('file-b', b => bufB = b, 'zone-b', 'name-b'); | |
| }); | |
| ['zone-a','zone-b'].forEach((id, i) => { | |
| const zone = document.getElementById(id); | |
| zone.addEventListener('dragover', e => { e.preventDefault(); zone.classList.add('dragover'); }); | |
| zone.addEventListener('dragleave', () => zone.classList.remove('dragover')); | |
| zone.addEventListener('drop', e => { | |
| e.preventDefault(); | |
| zone.classList.remove('dragover'); | |
| const fileInput = document.getElementById(i === 0 ? 'file-a' : 'file-b'); | |
| const dt = new DataTransfer(); | |
| dt.items.add(e.dataTransfer.files[0]); | |
| fileInput.files = dt.files; | |
| fileInput.dispatchEvent(new Event('change')); | |
| }); | |
| }); | |
| function compare() { | |
| if (!bufA || !bufB) return; | |
| const len = Math.max(bufA.length, bufB.length); | |
| allDiffs = []; | |
| for (let i = 0; i < len; i++) { | |
| const a = i < bufA.length ? bufA[i] : null; | |
| const b = i < bufB.length ? bufB[i] : null; | |
| if (a !== b) allDiffs.push({ offset: i, a, b }); | |
| } | |
| // Stats | |
| const diffCount = allDiffs.length; | |
| const sameCount = Math.min(bufA.length, bufB.length) - diffCount; | |
| // Diff regions (contiguous) | |
| let regions = 0; | |
| let inRegion = false; | |
| for (let i = 0; i < len; i++) { | |
| const isDiff = bufA[i] !== bufB[i]; | |
| if (isDiff && !inRegion) { regions++; inRegion = true; } | |
| else if (!isDiff) inRegion = false; | |
| } | |
| document.getElementById('stat-size').textContent = formatBytes(bufA.length); | |
| document.getElementById('stat-diff').textContent = diffCount.toLocaleString(); | |
| document.getElementById('stat-same').textContent = sameCount.toLocaleString(); | |
| document.getElementById('stat-regions').textContent = regions.toLocaleString(); | |
| document.getElementById('stats-bar').style.display = 'grid'; | |
| document.getElementById('filter-row').style.display = 'flex'; | |
| document.getElementById('results').style.display = 'block'; | |
| document.getElementById('export-btn').style.display = 'inline-block'; | |
| // Region tags | |
| const tagContainer = document.getElementById('region-tags'); | |
| tagContainer.innerHTML = ''; | |
| PS5_REGIONS.forEach(r => { | |
| const diffsInRegion = allDiffs.filter(d => d.offset >= r.start && d.offset < r.end).length; | |
| const tag = document.createElement('div'); | |
| tag.className = 'region-tag' + (diffsInRegion > 0 ? ' active' : ''); | |
| tag.textContent = `${r.name} (${diffsInRegion} diffs)`; | |
| tag.onclick = () => { | |
| document.getElementById('filter-from').value = r.start.toString(16).toUpperCase(); | |
| document.getElementById('filter-to').value = r.end.toString(16).toUpperCase(); | |
| renderTable(); | |
| }; | |
| tagContainer.appendChild(tag); | |
| }); | |
| renderTable(); | |
| } | |
| function renderTable() { | |
| const showSame = document.getElementById('show-same').checked; | |
| const fromHex = document.getElementById('filter-from').value.trim(); | |
| const toHex = document.getElementById('filter-to').value.trim(); | |
| const fromOff = fromHex ? parseInt(fromHex, 16) : 0; | |
| const toOff = toHex ? parseInt(toHex, 16) : Infinity; | |
| const len = Math.max(bufA.length, bufB.length); | |
| let rows = []; | |
| if (showSame) { | |
| for (let i = fromOff; i < Math.min(len, toOff); i++) { | |
| rows.push({ offset: i, a: bufA[i], b: bufB[i], diff: bufA[i] !== bufB[i] }); | |
| if (rows.length > 2000) break; | |
| } | |
| } else { | |
| rows = allDiffs | |
| .filter(d => d.offset >= fromOff && d.offset < toOff) | |
| .slice(0, 2000) | |
| .map(d => ({ ...d, diff: true })); | |
| } | |
| const body = document.getElementById('results-body'); | |
| if (rows.length === 0) { | |
| body.innerHTML = '<div class="no-results">// NO DIFFERENCES FOUND IN THIS RANGE</div>'; | |
| return; | |
| } | |
| body.innerHTML = rows.map(r => ` | |
| <div class="diff-row ${r.diff ? 'changed' : ''}"> | |
| <div class="offset">0x${r.offset.toString(16).toUpperCase().padStart(7,'0')}</div> | |
| <div class="val-a">${r.a != null ? r.a.toString(16).padStart(2,'0').toUpperCase() : '--'}</div> | |
| <div class="val-b">${r.b != null ? r.b.toString(16).padStart(2,'0').toUpperCase() : '--'}</div> | |
| <div><span class="badge ${r.diff ? 'diff' : 'same'}">${r.diff ? 'DIFF' : 'SAME'}</span></div> | |
| </div> | |
| `).join(''); | |
| if (rows.length >= 2000) { | |
| body.innerHTML += '<div class="no-results">// SHOWING FIRST 2000 RESULTS — USE FILTERS TO NARROW DOWN</div>'; | |
| } | |
| } | |
| function formatBytes(b) { | |
| if (b >= 1048576) return (b/1048576).toFixed(2) + ' MB'; | |
| if (b >= 1024) return (b/1024).toFixed(1) + ' KB'; | |
| return b + ' B'; | |
| } | |
| function exportCSV() { | |
| const lines = ['Offset,File_A,File_B,Status']; | |
| allDiffs.forEach(d => { | |
| lines.push(`0x${d.offset.toString(16).toUpperCase()},${d.a?.toString(16).toUpperCase()??'--'},${d.b?.toString(16).toUpperCase()??'--'},DIFF`); | |
| }); | |
| const blob = new Blob([lines.join('\n')], { type: 'text/csv' }); | |
| const a = document.createElement('a'); | |
| a.href = URL.createObjectURL(blob); | |
| a.download = 'hex_diff.csv'; | |
| a.click(); | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment