@@ 1,507 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <title>struct.pack editor</title>
+ <style>
+ :root {
+ --bg: #444;
+ }
+
+ * {
+ box-sizing: border-box;
+ }
+
+ textarea {
+ width: 100%;
+ height: 100%;
+ background: var(--bg);
+ border: none;
+ border-radius: 4px;
+ color: white;
+ padding: 8px;
+ }
+
+ html, body {
+ padding: 0;
+ margin: 0;
+ height: 100%;
+ background: #222;
+ color: white;
+ }
+
+ table {
+ width: 100%;
+ }
+
+ th {
+ text-align: left;
+ }
+
+ tbody tr:nth-child(odd) {
+ background-color: rgba(0, 0, 0, 0.1);
+ }
+
+ td {
+ font-family: monospace;
+ }
+
+ th {
+ font-family: sans-serif;
+ }
+
+ td, th {
+ padding: 4px;
+ }
+
+ tr.padding td {
+ color: #ccc;
+ }
+
+ .container {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-template-rows: 100px 1fr;
+ gap: 8px 8px;
+ grid-template-areas:
+ "entry data"
+ "format detail";
+ height: 100%;
+ padding: 8px;
+ }
+
+ .detail {
+ grid-area: detail;
+ background: var(--bg);
+ border-radius: 4px;
+ padding: 10px;
+ }
+
+ .entry {
+ grid-area: entry;
+ }
+
+ .format {
+ grid-area: format;
+ background: var(--bg);
+ border-radius: 4px;
+ }
+
+ .data {
+ grid-area: data;
+ }
+
+ #hexdump .group {
+ margin-right: 10px;
+ font-family: monospace;
+ }
+
+ #hexdump .byte {
+ padding-left: 3px;
+ padding-right: 3px;
+ }
+ </style>
+ </head>
+ <body>
+
+ <div class="container">
+ <div class="entry">
+ <textarea id="struct" placeholder="struct.pack('')"></textarea>
+ </div>
+ <div class="detail">
+ <div id="hexdump"></div>
+ </div>
+ <div class="format">
+ <table id="table">
+ <tbody></tbody>
+ </table>
+ </div>
+ <div class="data">
+ <textarea id="data" placeholder="48 65 6C 6C 6F"></textarea>
+ </div>
+ </div>
+
+ </body>
+ <script>
+ const colors = ["#e60049", "#0bb4ff", "#50e991", "#e6d800", "#9b19f5", "#ffa300", "#dc0ab4", "#b3d4ff", "#00bfa0"];
+ let endianness = '@';
+
+ function getContrast(hexcolor) {
+ hexcolor = hexcolor.slice(1);
+ const r = parseInt(hexcolor.substr(0, 2), 16);
+ const g = parseInt(hexcolor.substr(2, 2), 16);
+ const b = parseInt(hexcolor.substr(4, 2), 16);
+ const lightness = ((r * 299) + (g * 587) + (b * 114)) / 1000;
+ return lightness > 128;
+ }
+
+
+ function onChange() {
+ let format = updateFormatTable();
+ updateData(format);
+ }
+
+ document.addEventListener('DOMContentLoaded', function () {
+ const dataEntry = document.getElementById('data');
+ dataEntry.addEventListener('change', onChange);
+ dataEntry.addEventListener('keyup', onChange);
+
+ const formatEntry = document.getElementById('struct');
+ formatEntry.addEventListener('change', onChange);
+ formatEntry.addEventListener('keyup', onChange);
+
+ onChange();
+ });
+
+ function updateData(format) {
+ const dataEntry = document.getElementById('data');
+ let data = dataEntry.value;
+ data = data.toLowerCase().replace('0x', '').replace(/\s/g, '');
+
+ let bytes = [];
+ for (let i = 0; i < data.length; i += 2) {
+ bytes.push(parseInt(data.substring(i, i + 2), 16));
+ }
+
+ const hexdump = document.getElementById('hexdump');
+ hexdump.innerHTML = '';
+
+ for (let i = 0; i < bytes.length; i += 4) {
+ const group = document.createElement('SPAN');
+ group.classList.add('group');
+
+ for (let j = i; j < Math.min(i + 4, bytes.length); j++) {
+ const b = document.createElement('SPAN');
+ b.classList.add('byte');
+ b.innerText = bytes[j].toString(16).padStart(2, '0');
+
+ for (let k = 0; k < format.length; k++) {
+ if (j >= format[k]['pos'] && j < format[k]['pos'] + (format[k]['size'] * format[k]['count'])) {
+ b.style.backgroundColor = format[k]['color'];
+ if (getContrast(format[k]['color'])) {
+ b.style.color = '#000';
+ }
+ break;
+ }
+ }
+
+ group.appendChild(b);
+ }
+ hexdump.appendChild(group);
+ if (i % 8 === 4) {
+ hexdump.appendChild(document.createElement('BR'));
+ }
+ }
+
+ let dict = '{\n';
+ for (let i = 0; i < format.length; i++) {
+ const f = format[i];
+ if (f['format'] === 'x') {
+ continue;
+ }
+ const start = f['pos'];
+ const end = f['pos'] + (f['size'] * f['count']);
+ const raw = bytes.slice(start, end);
+ let rawdata = raw;
+ if (endianness === '>' || endianness === '!') {
+ rawdata = raw.reverse();
+ }
+ const rawb = new Uint8Array(rawdata);
+
+ let val = '';
+ switch (f['format']) {
+ case 'c':
+ val = raw[0];
+ break;
+ case 's':
+ val = JSON.stringify(raw);
+ break;
+ case 'b':
+ let tempb = new Int8Array(raw);
+ val = tempb[0];
+ break;
+ case 'B':
+ val = raw[0];
+ break;
+ case 'h':
+ let temph = new Int16Array(rawb.buffer);
+ val = temph[0];
+ break;
+ case 'H':
+ let tempH = new Uint16Array(rawb.buffer);
+ val = tempH[0];
+ break;
+ case 'i':
+ let tempi = new Int32Array(rawb.buffer);
+ val = tempi[0];
+ break;
+ case 'I':
+ let tempI = new Uint32Array(rawb.buffer);
+ val = tempI[0];
+ break;
+ case '?':
+ val = raw[0] !== 0;
+ break;
+ }
+ dict += ' "' + f['var'] + '": ' + val + '\n';
+ }
+
+ const dictElem = document.createElement('PRE');
+ dictElem.innerHTML = dict + "}";
+ hexdump.appendChild(dictElem);
+ }
+
+ function updateFormatTable() {
+ endianness = '@';
+ const table = document.getElementById('table');
+ const input = document.getElementById('struct');
+ const value = input.value.replace('struct.pack(', '').replace('struct.unpack(', '').replace(')', '').replace(';', '');
+
+ let new_tbody = document.createElement('tbody');
+ let old_tbody = document.querySelector('tbody');
+
+ let heading = new_tbody.insertRow();
+ heading.insertCell();
+ let th = document.createElement('TH');
+ th.innerText = 'format';
+ heading.appendChild(th);
+ th = document.createElement('TH');
+ th.innerText = 'offset';
+ heading.appendChild(th);
+ th = document.createElement('TH');
+ th.innerText = 'size';
+ heading.appendChild(th);
+ th = document.createElement('TH');
+ th.innerText = 'count';
+ heading.appendChild(th);
+ th = document.createElement('TH');
+ th.innerText = 'name';
+ heading.appendChild(th);
+ th = document.createElement('TH');
+ th.innerText = 'python type';
+ heading.appendChild(th);
+ th = document.createElement('TH');
+ th.innerText = 'variable';
+ heading.appendChild(th);
+
+ let pos = 0;
+ let number = '';
+ let parse_vars = false;
+ let var_slice = 0;
+ let quote = '';
+
+ let result = [];
+
+ for (let i = 0; i < value.length; i++) {
+ let c = value[i];
+ if (c === ' ' || c === '\n') {
+ continue;
+ }
+
+ if (c === '"' || c === "'") {
+ if (quote === '') {
+ quote = c;
+ continue;
+ }
+ parse_vars = true;
+ var_slice = i;
+ break;
+ }
+
+ let size = 0;
+ let name = '';
+ let ptype = 'int';
+ let split = true;
+
+ if (!isNaN(c)) {
+ number += c;
+ continue;
+ }
+
+ switch (c) {
+ case '@':
+ case '=':
+ case '<':
+ case '>':
+ case '!':
+ endianness = c;
+ continue;
+ case 'x':
+ size = 1;
+ name = 'padding';
+ split = false;
+ break;
+ case 'c':
+ size = 1;
+ name = 'char';
+ ptype = 'bytes'
+ break;
+ case 'b':
+ size = 1;
+ name = 'signed char';
+ break;
+ case 'B':
+ size = 1;
+ name = 'unsigned char';
+ break;
+ case '?':
+ size = 1;
+ name = 'bool';
+ ptype = 'bool'
+ break;
+ case 'h':
+ size = 2;
+ name = 'signed short';
+ break;
+ case 'H':
+ size = 2;
+ name = 'unsigned short';
+ break;
+ case 'i':
+ size = 4;
+ name = 'signed int';
+ break;
+ case 'I':
+ size = 4;
+ name = 'unsigned int';
+ break;
+ case 'l':
+ size = 4;
+ name = 'signed long';
+ break;
+ case 'L':
+ size = 4;
+ name = 'unsigned long';
+ break;
+ case 'q':
+ size = 8;
+ name = 'signed long long';
+ break;
+ case 'Q':
+ size = 8;
+ name = 'unsigned long long';
+ break;
+ case 'n':
+ size = 4;
+ name = 'ssize_t';
+ break;
+ case 'N':
+ size = 4;
+ name = 'size_t';
+ break;
+ case 'e':
+ size = 2;
+ name = 'float16';
+ ptype = 'float'
+ break;
+ case 'f':
+ size = 4;
+ name = 'float';
+ ptype = 'float';
+ break;
+ case 'd':
+ size = 8;
+ name = 'double';
+ ptype = 'float';
+ break;
+ case 's':
+ size = 1;
+ name = 'char[]';
+ ptype = 'bytes';
+ split = false;
+ break;
+ case 'p':
+ size = 1;
+ name = 'char[]';
+ ptype = 'bytes';
+ split = false;
+ break;
+ case 'P':
+ size = 1;
+ name = 'void';
+ ptype = 'int';
+ break;
+ }
+ let count = 1;
+ if (number !== '') {
+ count = parseInt(number, 10);
+ number = '';
+ }
+ if (split) {
+ for (let i = 0; i < count; i++) {
+ result.push({
+ 'format': c,
+ 'pos': pos + (i * size),
+ 'size': size,
+ 'count': 1,
+ 'name': name,
+ 'ptype': ptype,
+ 'var': '',
+ 'color': '',
+ });
+ }
+ } else {
+ result.push({
+ 'format': c,
+ 'pos': pos,
+ 'size': size,
+ 'count': count,
+ 'name': name,
+ 'ptype': ptype,
+ 'var': '',
+ 'color': '',
+ });
+ }
+ pos += size * count;
+ }
+
+ if (parse_vars) {
+ let vars = value.slice(var_slice + 1);
+ let part = vars.split(',');
+ let last_offset = 0;
+ for (let i = 1; i < part.length; i++) {
+ for (let j = last_offset; j < result.length; j++) {
+ if (result[j]['format'] === 'x') {
+ continue;
+ }
+ result[j]['var'] = part[i].trim();
+ last_offset = j + 1;
+ break;
+ }
+ }
+ }
+ let colorIndex = 0;
+ for (let i = 0; i < result.length; i++) {
+ let row = result[i];
+
+ let tr = new_tbody.insertRow();
+ if (row['format'] === 'x') {
+ tr.classList.add('padding');
+ }
+ let color = tr.insertCell();
+ if (row['format'] !== 'x') {
+ let col = colors[colorIndex % colors.length];
+ color.style.backgroundColor = col
+ result[i]['color'] = col;
+ colorIndex++;
+ }
+ let cell = tr.insertCell();
+ cell.innerText = row['format'];
+ cell = tr.insertCell();
+ cell.innerText = row['pos'];
+ cell = tr.insertCell();
+ cell.innerText = row['size'];
+ cell = tr.insertCell();
+ cell.innerText = row['count'];
+ cell = tr.insertCell();
+ cell.innerText = row['name'];
+ cell = tr.insertCell();
+ cell.innerText = row['ptype'];
+ cell = tr.insertCell();
+ cell.innerText = row['var'];
+ }
+
+ table.replaceChild(new_tbody, old_tbody);
+ return result;
+ }
+ </script>
+</html>