Files
MultiPROG-Ultimate-Kit/03_Script_Starter_Kit/binary_utils.mjs
T
2026-09-15 12:00:52 -07:00

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;
}