~martijnbraam/struct

a97ed818bf1f011544d40ec9f815089439c53461 — Martijn Braam 1 year, 1 month ago
Initial commit
1 files changed, 507 insertions(+), 0 deletions(-)

A index.html
A  => index.html +507 -0
@@ 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 += '&nbsp;&nbsp;"' + 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>