205 lines
5.6 KiB
JavaScript
205 lines
5.6 KiB
JavaScript
/* Dependency-free binary utilities for offline testing and authorized repair work.
|
|
Verify Multi-PROG host compatibility and API signatures in Local Script > Help. */
|
|
export function requireBytes(data) {
|
|
if (!(data instanceof Uint8Array)) throw new TypeError("Uint8Array required");
|
|
return data;
|
|
}
|
|
export function assertRange(data, offset, length) {
|
|
requireBytes(data);
|
|
if (
|
|
!Number.isInteger(offset) ||
|
|
!Number.isInteger(length) ||
|
|
offset < 0 ||
|
|
length < 0 ||
|
|
offset + length > data.length
|
|
)
|
|
throw new RangeError("Range outside buffer");
|
|
}
|
|
export function cloneBytes(data) {
|
|
requireBytes(data);
|
|
return new Uint8Array(data);
|
|
}
|
|
export function sliceBytes(data, offset, length) {
|
|
assertRange(data, offset, length);
|
|
return data.slice(offset, offset + length);
|
|
}
|
|
export function bytesToHex(data, separator = " ") {
|
|
requireBytes(data);
|
|
return Array.from(data, (b) => b.toString(16).padStart(2, "0"))
|
|
.join(separator)
|
|
.toUpperCase();
|
|
}
|
|
export function hexToBytes(text) {
|
|
const input = String(text);
|
|
if (/[^0-9a-f\s,:-]/i.test(input))
|
|
throw new Error("Invalid character in hex text");
|
|
const s = input.replace(/[\s,:-]/g, "");
|
|
if (s.length % 2) throw new Error("Hex text must contain complete bytes");
|
|
const out = new Uint8Array(s.length / 2);
|
|
for (let i = 0; i < out.length; i++)
|
|
out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
|
|
return out;
|
|
}
|
|
export function asciiPreview(data, replacement = ".") {
|
|
requireBytes(data);
|
|
return Array.from(data, (b) =>
|
|
b >= 32 && b <= 126 ? String.fromCharCode(b) : replacement,
|
|
).join("");
|
|
}
|
|
export function readU16LE(d, o) {
|
|
assertRange(d, o, 2);
|
|
return d[o] | (d[o + 1] << 8);
|
|
}
|
|
export function readU16BE(d, o) {
|
|
assertRange(d, o, 2);
|
|
return (d[o] << 8) | d[o + 1];
|
|
}
|
|
export function readU32LE(d, o) {
|
|
assertRange(d, o, 4);
|
|
return (d[o] | (d[o + 1] << 8) | (d[o + 2] << 16) | (d[o + 3] << 24)) >>> 0;
|
|
}
|
|
export function readU32BE(d, o) {
|
|
assertRange(d, o, 4);
|
|
return ((d[o] << 24) | (d[o + 1] << 16) | (d[o + 2] << 8) | d[o + 3]) >>> 0;
|
|
}
|
|
export function writeU16LE(d, o, v) {
|
|
assertRange(d, o, 2);
|
|
d[o] = v & 255;
|
|
d[o + 1] = (v >>> 8) & 255;
|
|
}
|
|
export function writeU16BE(d, o, v) {
|
|
assertRange(d, o, 2);
|
|
d[o] = (v >>> 8) & 255;
|
|
d[o + 1] = v & 255;
|
|
}
|
|
export function writeU32LE(d, o, v) {
|
|
assertRange(d, o, 4);
|
|
for (let i = 0; i < 4; i++) d[o + i] = (v >>> (8 * i)) & 255;
|
|
}
|
|
export function writeU32BE(d, o, v) {
|
|
assertRange(d, o, 4);
|
|
for (let i = 0; i < 4; i++) d[o + i] = (v >>> (8 * (3 - i))) & 255;
|
|
}
|
|
export function findPattern(data, pattern, start = 0) {
|
|
requireBytes(data);
|
|
requireBytes(pattern);
|
|
const hits = [];
|
|
for (let i = start; i <= data.length - pattern.length; i++) {
|
|
let ok = true;
|
|
for (let j = 0; j < pattern.length; j++)
|
|
if (data[i + j] !== pattern[j]) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
if (ok) hits.push(i);
|
|
}
|
|
return hits;
|
|
}
|
|
export function countByte(data, value) {
|
|
requireBytes(data);
|
|
let n = 0;
|
|
for (const b of data) if (b === (value & 255)) n++;
|
|
return n;
|
|
}
|
|
export function isBlank(
|
|
data,
|
|
offset = 0,
|
|
length = data.length - offset,
|
|
blank = 0xff,
|
|
) {
|
|
assertRange(data, offset, length);
|
|
for (let i = offset; i < offset + length; i++)
|
|
if (data[i] !== blank) return false;
|
|
return true;
|
|
}
|
|
export function diffRanges(a, b) {
|
|
requireBytes(a);
|
|
requireBytes(b);
|
|
if (a.length !== b.length) throw new Error("Length mismatch");
|
|
const out = [];
|
|
let s = -1;
|
|
for (let i = 0; i < a.length; i++) {
|
|
if (a[i] !== b[i] && s < 0) s = i;
|
|
if ((a[i] === b[i] || i === a.length - 1) && s >= 0) {
|
|
const e = a[i] === b[i] ? i - 1 : i;
|
|
out.push({ start: s, end: e, length: e - s + 1 });
|
|
s = -1;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
export function changedByteCount(a, b) {
|
|
return diffRanges(a, b).reduce((n, r) => n + r.length, 0);
|
|
}
|
|
export function sum8(data) {
|
|
requireBytes(data);
|
|
let s = 0;
|
|
for (const b of data) s = (s + b) & 255;
|
|
return s;
|
|
}
|
|
export function xor8(data) {
|
|
requireBytes(data);
|
|
let x = 0;
|
|
for (const b of data) x ^= b;
|
|
return x;
|
|
}
|
|
export function crc16CcittFalse(data) {
|
|
requireBytes(data);
|
|
let crc = 0xffff;
|
|
for (const b of data) {
|
|
crc ^= b << 8;
|
|
for (let i = 0; i < 8; i++)
|
|
crc = (crc & 0x8000 ? (crc << 1) ^ 0x1021 : crc << 1) & 0xffff;
|
|
}
|
|
return crc;
|
|
}
|
|
export function crc32(data) {
|
|
requireBytes(data);
|
|
let crc = 0xffffffff;
|
|
for (const b of data) {
|
|
crc ^= b;
|
|
for (let i = 0; i < 8; i++) crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
|
|
}
|
|
return (crc ^ 0xffffffff) >>> 0;
|
|
}
|
|
export function adler32(data) {
|
|
requireBytes(data);
|
|
let a = 1,
|
|
b = 0;
|
|
for (const v of data) {
|
|
a = (a + v) % 65521;
|
|
b = (b + a) % 65521;
|
|
}
|
|
return ((b << 16) | a) >>> 0;
|
|
}
|
|
export function bcdToInt(byte) {
|
|
const hi = (byte >>> 4) & 15,
|
|
lo = byte & 15;
|
|
if (hi > 9 || lo > 9) throw new Error("Invalid BCD");
|
|
return hi * 10 + lo;
|
|
}
|
|
export function intToBcd(value) {
|
|
if (!Number.isInteger(value) || value < 0 || value > 99)
|
|
throw new RangeError("BCD value must be 0..99");
|
|
return (Math.floor(value / 10) << 4) | (value % 10);
|
|
}
|
|
export function constantTimeEqual(a, b) {
|
|
requireBytes(a);
|
|
requireBytes(b);
|
|
if (a.length !== b.length) return false;
|
|
let d = 0;
|
|
for (let i = 0; i < a.length; i++) d |= a[i] ^ b[i];
|
|
return d === 0;
|
|
}
|
|
export function allowListedWrite(copy, offset, newBytes, allowedRanges) {
|
|
requireBytes(copy);
|
|
requireBytes(newBytes);
|
|
assertRange(copy, offset, newBytes.length);
|
|
const ok = allowedRanges.some(
|
|
(r) => offset >= r.start && offset + newBytes.length - 1 <= r.end,
|
|
);
|
|
if (!ok) throw new Error("Write is outside allow-list");
|
|
copy.set(newBytes, offset);
|
|
return copy;
|
|
}
|