← Files ModRetro Chromatic PluginARCHIVED FILE

dist/resource-codec.js

4.63 KB · Oct 2, 2026 · 00:37 UTC

↓ Download file

export class ResourceCodecError extends Error {
    code;
    constructor(code, message) {
        super(message);
        this.code = code;
        this.name = "ResourceCodecError";
    }
}
function hexadecimalDigit(character) {
    const code = character.charCodeAt(0);
    if (code >= 48 && code <= 57)
        return code - 48;
    if (code >= 65 && code <= 70)
        return code - 55;
    if (code >= 97 && code <= 102)
        return code - 87;
    return -1;
}
function invalidEncoding(position, reason) {
    throw new ResourceCodecError("INVALID_RESOURCE_ENCODING", `Invalid native resource byte-run encoding at character ${position}: ${reason}`);
}
/** Decode the hexadecimal byte-run format used by collision and tile-color resources. */
export function decodeResourceBytes(encoded, options) {
    if (!Number.isSafeInteger(options.maximumValues) || options.maximumValues < 0) {
        throw new ResourceCodecError("RESOURCE_VALUE_LIMIT", "The maximum number of decoded native project resource bytes must be a nonnegative safe integer");
    }
    if (encoded.length === 0) {
        if (options.allowImplicitZeroGrid)
            return new Uint8Array();
        invalidEncoding(0, "an implicit zero grid is not allowed");
    }
    const runs = [];
    let position = 0;
    let totalValues = 0;
    while (position < encoded.length) {
        const valuePosition = position;
        if (position + 1 >= encoded.length) {
            invalidEncoding(valuePosition, "each byte requires exactly two hexadecimal digits");
        }
        const highDigit = hexadecimalDigit(encoded[position] ?? "");
        const lowDigit = hexadecimalDigit(encoded[position + 1] ?? "");
        if (highDigit < 0 || lowDigit < 0) {
            invalidEncoding(valuePosition, "each byte requires exactly two hexadecimal digits");
        }
        const value = highDigit * 16 + lowDigit;
        position += 2;
        if (position >= encoded.length) {
            invalidEncoding(position, "a byte must end with ! or a hexadecimal run length followed by +");
        }
        let count = 0;
        if (encoded[position] === "!") {
            count = 1;
            position += 1;
        }
        else {
            const runPosition = position;
            let digits = 0;
            while (position < encoded.length && encoded[position] !== "+") {
                const digit = hexadecimalDigit(encoded[position] ?? "");
                if (digit < 0) {
                    invalidEncoding(position, "a run length must contain only hexadecimal digits");
                }
                if (count > Math.floor((Number.MAX_SAFE_INTEGER - digit) / 16)) {
                    throw new ResourceCodecError("RESOURCE_VALUE_LIMIT", "A native resource byte run exceeds the maximum safe integer");
                }
                count = count * 16 + digit;
                digits += 1;
                if (count > options.maximumValues - totalValues) {
                    throw new ResourceCodecError("RESOURCE_VALUE_LIMIT", `Decoded native project resource exceeds its ${options.maximumValues}-byte limit`);
                }
                position += 1;
            }
            if (digits === 0 || encoded[position] !== "+") {
                invalidEncoding(runPosition, "a run requires a hexadecimal length followed by +");
            }
            if (count === 0) {
                invalidEncoding(runPosition, "a byte run must have a positive length");
            }
            position += 1;
        }
        if (count > options.maximumValues - totalValues) {
            throw new ResourceCodecError("RESOURCE_VALUE_LIMIT", `Decoded native project resource exceeds its ${options.maximumValues}-byte limit`);
        }
        totalValues += count;
        runs.push({ value, count });
    }
    const values = new Uint8Array(totalValues);
    let offset = 0;
    for (const { value, count } of runs) {
        values.fill(value, offset, offset + count);
        offset += count;
    }
    return values;
}
/** Encode bytes without changing any opaque collision bits or palette-slot values. */
export function encodeResourceBytes(values) {
    if (values.length === 0)
        return "";
    let encoded = "";
    let previous = values[0] ?? 0;
    let count = 1;
    for (let index = 1; index <= values.length; index += 1) {
        const value = values[index];
        if (index < values.length && value === previous) {
            count += 1;
            continue;
        }
        encoded += previous.toString(16).padStart(2, "0");
        encoded += count === 1 ? "!" : `${count.toString(16)}+`;
        previous = value ?? 0;
        count = 1;
    }
    return encoded;
}
//# sourceMappingURL=resource-codec.js.map

SHA-256: b225536a083e85ed97d4be3dd6f44b2ae922d01a040ee71a3c60b39e601584e9