Skip to content

Instantly share code, notes, and snippets.

@acaburaz
Created March 10, 2026 10:17
Show Gist options
  • Select an option

  • Save acaburaz/ed2d061f8408e17123a56089c6f41d39 to your computer and use it in GitHub Desktop.

Select an option

Save acaburaz/ed2d061f8408e17123a56089c6f41d39 to your computer and use it in GitHub Desktop.
<!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