Files
minisrv/zefie_wtvp_minisrv/includes/classes/LZPF.js
2025-07-23 22:56:18 -04:00

688 lines
33 KiB
JavaScript

/**
* A Node.js port of the WebTV LZPF compression and expansion algorithm.
*/
class LZPF {
/**
* Initializes the Lzpf instance, setting up the necessary data tables
* and clearing the context for a new operation.
*/
constructor() {
// Static tables used by the compression and expansion algorithms.
// These are ported directly from the Python implementation.
this.tables = {
bitMask: [
0x00000000, 0x80000000, 0xC0000000, 0xE0000000,
0xF0000000, 0xF8000000, 0xFC000000, 0xFE000000,
0xFF000000, 0xFF800000, 0xFFC00000, 0xFFE00000,
0xFFF00000, 0xFFF80000, 0xFFFC0000, 0xFFFE0000,
0xFFFF0000, 0xFFFF8000, 0xFFFFC000, 0xFFFFE000,
0xFFFFF000, 0xFFFFF800, 0xFFFFFC00, 0xFFFFFE00,
0xFFFFFF00, 0xFFFFFF80, 0xFFFFFFC0, 0xFFFFFFE0,
0xFFFFFFF0, 0xFFFFFFF8, 0xFFFFFFFC, 0xFFFFFFFE,
0xFFFFFFFF
],
huff_lits: [
0x20, 0x65, 0x0A, 0x22, 0x2F, 0x3C, 0x3E, 0x54,
0x61, 0x69, 0x6E, 0x6F, 0x70, 0x72, 0x73, 0x74,
0x09, 0x2E, 0x3D, 0x41, 0x45, 0x49, 0x4E, 0x4F,
0x52, 0x63, 0x64, 0x68, 0x6C, 0x6D, 0x77, 0x2C,
0x2D, 0x31, 0x42, 0x43, 0x44, 0x46, 0x47, 0x48,
0x4C, 0x50, 0x53, 0x62, 0x66, 0x67, 0x75, 0x30,
0x32, 0x34, 0x3A, 0x4D, 0x57, 0x5F, 0x6B, 0x76,
0x79, 0x0D, 0x23, 0x27, 0x33, 0x35, 0x36, 0x37,
0x38, 0x39, 0x55, 0x56, 0x5A, 0x78, 0x21, 0x2A,
0x3F, 0x4B, 0x59, 0x7A, 0x26, 0x28, 0x29, 0x3B,
0x4A, 0x6A, 0x71, 0x51, 0x58, 0x7B, 0x7C, 0x7D,
0x7E, 0x2B, 0x24, 0x40, 0x05, 0x07, 0x08, 0x0B,
0x17, 0x91, 0x92, 0x93, 0xC7, 0xF3, 0xFA, 0x00,
0x01, 0x02, 0x03, 0x04, 0x06, 0x0C, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x18,
0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x25,
0x5B, 0x5C, 0x5D, 0x5E, 0x60, 0x7F, 0x80, 0x81,
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x94,
0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C,
0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4,
0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC,
0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4,
0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC,
0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4,
0xC5, 0xC6, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD,
0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5,
0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD,
0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5,
0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED,
0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF4, 0xF5, 0xF6,
0xF7, 0xF8, 0xF9, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
],
parents: [
0x00, 0x02, 0x04, 0x08, 0x0E, 0x0E, 0x0D, 0x0A,
0x0A, 0x07, 0x08, 0x09, 0x0C, 0x17, 0x2C, 0x4D,
0x00, 0x00, 0x00, 0x00,
],
// Pre-converted to signed 8-bit integers to match Python's ctypes.c_byte behavior
lit_base: [
0, -2, -4, -8, -14, -12, 3, 21, 37, 50, 62, 67, 71, 66, 46, 15, 103, 0, 0, 0
],
peekTable: [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04
],
matchDecode: [
0x01, 0x03, 0x01, 0x03, 0x01, 0x03, 0x01, 0x03,
0x01, 0x03, 0x01, 0x03, 0x01, 0x03, 0x01, 0x03,
0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03,
0x02, 0x03, 0x02, 0x03, 0x02, 0x03, 0x02, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x04, 0x06, 0x05, 0x06, 0x06, 0x06, 0x07, 0x06,
0x08, 0x06, 0x09, 0x06, 0x0A, 0x06, 0x00, 0x00
],
nomatchEncode: [
[0x0000, 0x10], [0x0001, 0x10], [0x0002, 0x10],
[0x0003, 0x10], [0x0004, 0x10], [0x009A, 0x0F],
[0x0005, 0x10], [0x009C, 0x0F], [0x009E, 0x0F],
[0x3400, 0x06], [0x7000, 0x05], [0x00A0, 0x0F],
[0x0006, 0x10], [0x0380, 0x09], [0x0007, 0x10],
[0x0008, 0x10], [0x0009, 0x10], [0x000A, 0x10],
[0x000B, 0x10], [0x000C, 0x10], [0x000D, 0x10],
[0x000E, 0x10], [0x000F, 0x10], [0x00A2, 0x0F],
[0x0010, 0x10], [0x0011, 0x10], [0x0012, 0x10],
[0x0013, 0x10], [0x0014, 0x10], [0x0015, 0x10],
[0x0016, 0x10], [0x0017, 0x10], [0xE000, 0x04],
[0x0200, 0x0A], [0x7800, 0x05], [0x0400, 0x09],
[0x00B0, 0x0E], [0x0018, 0x10], [0x0120, 0x0B],
[0x0480, 0x09], [0x0140, 0x0B], [0x0160, 0x0B],
[0x0240, 0x0A], [0x00B8, 0x0D], [0x1400, 0x07],
[0x1600, 0x07], [0x3800, 0x06], [0x8000, 0x05],
[0x0A00, 0x08], [0x1800, 0x07], [0x0B00, 0x08],
[0x0500, 0x09], [0x0C00, 0x08], [0x0580, 0x09],
[0x0600, 0x09], [0x0680, 0x09], [0x0700, 0x09],
[0x0780, 0x09], [0x0D00, 0x08], [0x0180, 0x0B],
[0x8800, 0x05], [0x3C00, 0x06], [0x9000, 0x05],
[0x0280, 0x0A], [0x00B4, 0x0E], [0x4000, 0x06],
[0x1A00, 0x07], [0x1C00, 0x07], [0x1E00, 0x07],
[0x4400, 0x06], [0x2000, 0x07], [0x2200, 0x07],
[0x2400, 0x07], [0x4800, 0x06], [0x01A0, 0x0B],
[0x02C0, 0x0A], [0x2600, 0x07], [0x0E00, 0x08],
[0x4C00, 0x06], [0x5000, 0x06], [0x2800, 0x07],
[0x00C0, 0x0C], [0x5400, 0x06], [0x2A00, 0x07],
[0x9800, 0x05], [0x0800, 0x09], [0x0880, 0x09],
[0x0F00, 0x08], [0x00D0, 0x0C], [0x0300, 0x0A],
[0x0900, 0x09], [0x0019, 0x10], [0x001A, 0x10],
[0x001B, 0x10], [0x001C, 0x10], [0x1000, 0x08],
[0x001D, 0x10], [0xA000, 0x05], [0x2C00, 0x07],
[0x5800, 0x06], [0x5C00, 0x06], [0xF000, 0x04],
[0x2E00, 0x07], [0x3000, 0x07], [0x6000, 0x06],
[0xA800, 0x05], [0x01C0, 0x0B], [0x1100, 0x08],
[0x6400, 0x06], [0x6800, 0x06], [0xB000, 0x05],
[0xB800, 0x05], [0xC000, 0x05], [0x01E0, 0x0B],
[0xC800, 0x05], [0xD000, 0x05], [0xD800, 0x05],
[0x3200, 0x07], [0x1200, 0x08], [0x6C00, 0x06],
[0x0980, 0x09], [0x1300, 0x08], [0x0340, 0x0A],
[0x00E0, 0x0C], [0x00F0, 0x0C], [0x0100, 0x0C],
[0x0110, 0x0C], [0x001E, 0x10], [0x001F, 0x10],
[0x0020, 0x10], [0x0021, 0x10], [0x0022, 0x10],
[0x0023, 0x10], [0x0024, 0x10], [0x0025, 0x10],
[0x0026, 0x10], [0x0027, 0x10], [0x0028, 0x10],
[0x0029, 0x10], [0x002A, 0x10], [0x002B, 0x10],
[0x002C, 0x10], [0x002D, 0x10], [0x002E, 0x10],
[0x002F, 0x10], [0x00A4, 0x0F], [0x00A6, 0x0F],
[0x00A8, 0x0F], [0x0030, 0x10], [0x0031, 0x10],
[0x0032, 0x10], [0x0033, 0x10], [0x0034, 0x10],
[0x0035, 0x10], [0x0036, 0x10], [0x0037, 0x10],
[0x0038, 0x10], [0x0039, 0x10], [0x003A, 0x10],
[0x003B, 0x10], [0x003C, 0x10], [0x003D, 0x10],
[0x003E, 0x10], [0x003F, 0x10], [0x0040, 0x10],
[0x0041, 0x10], [0x0042, 0x10], [0x0043, 0x10],
[0x0044, 0x10], [0x0045, 0x10], [0x0046, 0x10],
[0x0047, 0x10], [0x0048, 0x10], [0x0049, 0x10],
[0x004A, 0x10], [0x004B, 0x10], [0x004C, 0x10],
[0x004D, 0x10], [0x004E, 0x10], [0x004F, 0x10],
[0x0050, 0x10], [0x0051, 0x10], [0x0052, 0x10],
[0x0053, 0x10], [0x0054, 0x10], [0x0055, 0x10],
[0x0056, 0x10], [0x0057, 0x10], [0x0058, 0x10],
[0x0059, 0x10], [0x005A, 0x10], [0x005B, 0x10],
[0x005C, 0x10], [0x005D, 0x10], [0x005E, 0x10],
[0x005F, 0x10], [0x0060, 0x10], [0x0061, 0x10],
[0x0062, 0x10], [0x00AA, 0x0F], [0x0063, 0x10],
[0x0064, 0x10], [0x0065, 0x10], [0x0066, 0x10],
[0x0067, 0x10], [0x0068, 0x10], [0x0069, 0x10],
[0x006A, 0x10], [0x006B, 0x10], [0x006C, 0x10],
[0x006D, 0x10], [0x006E, 0x10], [0x006F, 0x10],
[0x0070, 0x10], [0x0071, 0x10], [0x0072, 0x10],
[0x0073, 0x10], [0x0074, 0x10], [0x0075, 0x10],
[0x0076, 0x10], [0x0077, 0x10], [0x0078, 0x10],
[0x0079, 0x10], [0x007A, 0x10], [0x007B, 0x10],
[0x007C, 0x10], [0x007D, 0x10], [0x007E, 0x10],
[0x007F, 0x10], [0x0080, 0x10], [0x0081, 0x10],
[0x0082, 0x10], [0x0083, 0x10], [0x0084, 0x10],
[0x0085, 0x10], [0x0086, 0x10], [0x0087, 0x10],
[0x0088, 0x10], [0x0089, 0x10], [0x008A, 0x10],
[0x008B, 0x10], [0x008C, 0x10], [0x008D, 0x10],
[0x00AC, 0x0F], [0x008E, 0x10], [0x008F, 0x10],
[0x0090, 0x10], [0x0091, 0x10], [0x0092, 0x10],
[0x0093, 0x10], [0x00AE, 0x0F], [0x0094, 0x10],
[0x0095, 0x10], [0x0096, 0x10], [0x0097, 0x10],
[0x0098, 0x10], [0x0099, 0x10]
],
matchEncode: [
[0x80000000, 0x01], [0x80000000, 0x03],
[0xA0000000, 0x03], [0xC0000000, 0x03],
[0xE0000000, 0x06], [0xE4000000, 0x06],
[0xE8000000, 0x06], [0xEC000000, 0x06],
[0xF0000000, 0x06], [0xF4000000, 0x06],
[0xF8000000, 0x06], [0xFC000000, 0x0B],
[0xFC200000, 0x0B], [0xFC400000, 0x0B],
[0xFC600000, 0x0B], [0xFC800000, 0x0B],
[0xFCA00000, 0x0B], [0xFCC00000, 0x0B],
[0xFCE00000, 0x0B], [0xFD000000, 0x0B],
[0xFD200000, 0x0B], [0xFD400000, 0x0B],
[0xFD600000, 0x0B], [0xFD800000, 0x0B],
[0xFDA00000, 0x0B], [0xFDC00000, 0x0B],
[0xFDE00000, 0x0B], [0xFE000000, 0x0B],
[0xFE200000, 0x0B], [0xFE400000, 0x0B],
[0xFE600000, 0x0B], [0xFE800000, 0x0B],
[0xFEA00000, 0x0B], [0xFEC00000, 0x0B],
[0xFEE00000, 0x0B], [0xFF000000, 0x0B],
[0xFF200000, 0x0B], [0xFF400000, 0x0B],
[0xFF600000, 0x0B], [0xFF800000, 0x0B],
[0xFFA00000, 0x0B], [0xFFC00000, 0x0B],
[0xFFE00000, 0x13], [0xFFE02000, 0x13],
[0xFFE04000, 0x13], [0xFFE06000, 0x13],
[0xFFE08000, 0x13], [0xFFE0A000, 0x13],
[0xFFE0C000, 0x13], [0xFFE0E000, 0x13],
[0xFFE10000, 0x13], [0xFFE12000, 0x13],
[0xFFE14000, 0x13], [0xFFE16000, 0x13],
[0xFFE18000, 0x13], [0xFFE1A000, 0x13],
[0xFFE1C000, 0x13], [0xFFE1E000, 0x13],
[0xFFE20000, 0x13], [0xFFE22000, 0x13],
[0xFFE24000, 0x13], [0xFFE26000, 0x13],
[0xFFE28000, 0x13], [0xFFE2A000, 0x13],
[0xFFE2C000, 0x13], [0xFFE2E000, 0x13],
[0xFFE30000, 0x13], [0xFFE32000, 0x13],
[0xFFE34000, 0x13], [0xFFE36000, 0x13],
[0xFFE38000, 0x13], [0xFFE3A000, 0x13],
[0xFFE3C000, 0x13], [0xFFE3E000, 0x13],
[0xFFE40000, 0x13], [0xFFE42000, 0x13],
[0xFFE44000, 0x13], [0xFFE46000, 0x13],
[0xFFE48000, 0x13], [0xFFE4A000, 0x13],
[0xFFE4C000, 0x13], [0xFFE4E000, 0x13],
[0xFFE50000, 0x13], [0xFFE52000, 0x13],
[0xFFE54000, 0x13], [0xFFE56000, 0x13],
[0xFFE58000, 0x13], [0xFFE5A000, 0x13],
[0xFFE5C000, 0x13], [0xFFE5E000, 0x13],
[0xFFE60000, 0x13], [0xFFE62000, 0x13],
[0xFFE64000, 0x13], [0xFFE66000, 0x13],
[0xFFE68000, 0x13], [0xFFE6A000, 0x13],
[0xFFE6C000, 0x13], [0xFFE6E000, 0x13],
[0xFFE70000, 0x13], [0xFFE72000, 0x13],
[0xFFE74000, 0x13], [0xFFE76000, 0x13],
[0xFFE78000, 0x13], [0xFFE7A000, 0x13],
[0xFFE7C000, 0x13], [0xFFE7E000, 0x13],
[0xFFE80000, 0x13], [0xFFE82000, 0x13],
[0xFFE84000, 0x13], [0xFFE86000, 0x13],
[0xFFE88000, 0x13], [0xFFE8A000, 0x13],
[0xFFE8C000, 0x13], [0xFFE8E000, 0x13],
[0xFFE90000, 0x13], [0xFFE92000, 0x13],
[0xFFE94000, 0x13], [0xFFE96000, 0x13],
[0xFFE98000, 0x13], [0xFFE9A000, 0x13],
[0xFFE9C000, 0x13], [0xFFE9E000, 0x13],
[0xFFEA0000, 0x13], [0xFFEA2000, 0x13],
[0xFFEA4000, 0x13], [0xFFEA6000, 0x13],
[0xFFEA8000, 0x13], [0xFFEAA000, 0x13],
[0xFFEAC000, 0x13], [0xFFEAE000, 0x13],
[0xFFEB0000, 0x13], [0xFFEB2000, 0x13],
[0xFFEB4000, 0x13], [0xFFEB6000, 0x13],
[0xFFEB8000, 0x13], [0xFFEBA000, 0x13],
[0xFFEBC000, 0x13], [0xFFEBE000, 0x13],
[0xFFEC0000, 0x13], [0xFFEC2000, 0x13],
[0xFFEC4000, 0x13], [0xFFEC6000, 0x13],
[0xFFEC8000, 0x13], [0xFFECA000, 0x13],
[0xFFECC000, 0x13], [0xFFECE000, 0x13],
[0xFFED0000, 0x13], [0xFFED2000, 0x13],
[0xFFED4000, 0x13], [0xFFED6000, 0x13],
[0xFFED8000, 0x13], [0xFFEDA000, 0x13],
[0xFFEDC000, 0x13], [0xFFEDE000, 0x13],
[0xFFEE0000, 0x13], [0xFFEE2000, 0x13],
[0xFFEE4000, 0x13], [0xFFEE6000, 0x13],
[0xFFEE8000, 0x13], [0xFFEEA000, 0x13],
[0xFFEEC000, 0x13], [0xFFEEE000, 0x13],
[0xFFEF0000, 0x13], [0xFFEF2000, 0x13],
[0xFFEF4000, 0x13], [0xFFEF6000, 0x13],
[0xFFEF8000, 0x13], [0xFFEFA000, 0x13],
[0xFFEFC000, 0x13], [0xFFEFE000, 0x13],
[0xFFF00000, 0x13], [0xFFF02000, 0x13],
[0xFFF04000, 0x13], [0xFFF06000, 0x13],
[0xFFF08000, 0x13], [0xFFF0A000, 0x13],
[0xFFF0C000, 0x13], [0xFFF0E000, 0x13],
[0xFFF10000, 0x13], [0xFFF12000, 0x13],
[0xFFF14000, 0x13], [0xFFF16000, 0x13],
[0xFFF18000, 0x13], [0xFFF1A000, 0x13],
[0xFFF1C000, 0x13], [0xFFF1E000, 0x13],
[0xFFF20000, 0x13], [0xFFF22000, 0x13],
[0xFFF24000, 0x13], [0xFFF26000, 0x13],
[0xFFF28000, 0x13], [0xFFF2A000, 0x13],
[0xFFF2C000, 0x13], [0xFFF2E000, 0x13],
[0xFFF30000, 0x13], [0xFFF32000, 0x13],
[0xFFF34000, 0x13], [0xFFF36000, 0x13],
[0xFFF38000, 0x13], [0xFFF3A000, 0x13],
[0xFFF3C000, 0x13], [0xFFF3E000, 0x13],
[0xFFF40000, 0x13], [0xFFF42000, 0x13],
[0xFFF44000, 0x13], [0xFFF46000, 0x13],
[0xFFF48000, 0x13], [0xFFF4A000, 0x13],
[0xFFF4C000, 0x13], [0xFFF4E000, 0x13],
[0xFFF50000, 0x13], [0xFFF52000, 0x13],
[0xFFF54000, 0x13], [0xFFF56000, 0x13],
[0xFFF58000, 0x13], [0xFFF5A000, 0x13],
[0xFFF5C000, 0x13], [0xFFF5E000, 0x13],
[0xFFF60000, 0x13], [0xFFF62000, 0x13],
[0xFFF64000, 0x13], [0xFFF66000, 0x13],
[0xFFF68000, 0x13], [0xFFF6A000, 0x13],
[0xFFF6C000, 0x13], [0xFFF6E000, 0x13],
[0xFFF70000, 0x13], [0xFFF72000, 0x13],
[0xFFF74000, 0x13], [0xFFF76000, 0x13],
[0xFFF78000, 0x13], [0xFFF7A000, 0x13],
[0xFFF7C000, 0x13], [0xFFF7E000, 0x13],
[0xFFF80000, 0x13], [0xFFF82000, 0x13],
[0xFFF84000, 0x13], [0xFFF86000, 0x13],
[0xFFF88000, 0x13], [0xFFF8A000, 0x13],
[0xFFF8C000, 0x13], [0xFFF8E000, 0x13],
[0xFFF90000, 0x13], [0xFFF92000, 0x13],
[0xFFF94000, 0x13], [0xFFF96000, 0x13],
[0xFFF98000, 0x13], [0xFFF9A000, 0x13],
[0xFFF9C000, 0x13], [0xFFF9E000, 0x13],
[0xFFFA0000, 0x13], [0xFFFA2000, 0x13],
[0xFFFA4000, 0x13], [0xFFFA6000, 0x13],
[0xFFFA8000, 0x13], [0xFFFAA000, 0x13],
[0xFFFAC000, 0x13], [0xFFFAE000, 0x13],
[0xFFFB0000, 0x13], [0xFFFB2000, 0x13],
[0xFFFB4000, 0x13], [0xFFFB6000, 0x13],
[0xFFFB8000, 0x13], [0xFFFBA000, 0x13],
[0xFFFBC000, 0x13], [0xFFFBE000, 0x13],
[0xFFFC0000, 0x13], [0xFFFC2000, 0x13],
[0xFFFC4000, 0x13], [0xFFFC6000, 0x13],
[0xFFFC8000, 0x13], [0xFFFCA000, 0x13],
[0xFFFCC000, 0x13], [0xFFFCE000, 0x13],
[0xFFFD0000, 0x13], [0xFFFD2000, 0x13],
[0xFFFD4000, 0x13], [0xFFFD6000, 0x13],
[0xFFFD8000, 0x13], [0xFFFDA000, 0x13],
[0xFFFDC000, 0x13], [0xFFFDE000, 0x13],
[0xFFFE0000, 0x13], [0xFFFE2000, 0x13],
[0xFFFE4000, 0x13], [0xFFFE6000, 0x13],
[0xFFFE8000, 0x13], [0xFFFEA000, 0x13],
[0xFFFEC000, 0x13], [0xFFFEE000, 0x13],
[0xFFFF0000, 0x13], [0xFFFF2000, 0x13],
[0xFFFF4000, 0x13], [0xFFFF6000, 0x13],
[0xFFFF8000, 0x13], [0xFFFFA000, 0x13],
[0xFFFFC000, 0x13], [0xFFFFE000, 0x13],
]
};
this.clear();
}
/**
* Resets the compression/expansion context to its initial state.
* This is called automatically by the constructor and before starting a new compression or expansion.
*/
clear() {
this.context = {
current_literal: 0,
current_length: 0,
compressed_data: [],
filler_byte: 0x00,
ring_buffer: Buffer.alloc(0x2000),
flag_table: new Array(0x1000).fill(0xFFFF),
};
}
/**
* Encodes a literal value into the compressed data stream.
* @param {number} code_length - The number of bits in the code.
* @param {number} code - The code to be written.
*/
encodeLiteral(code_length, code) {
// JavaScript handles large integers, but we ensure 32-bit unsigned behavior for consistency
// by using the unsigned right shift operator (>>>).
this.context.current_literal |= (code >>> (this.context.current_length & 0x1F));
this.context.current_length += code_length;
// Write full bytes to the output buffer as they are completed.
while (this.context.current_length > 7) {
this.context.compressed_data.push((this.context.current_literal >>> 0x18) & 0xFF);
this.context.current_length -= 8;
this.context.current_literal <<= 8;
}
}
/**
* Compresses the input data using the LZPF algorithm.
* @param {Buffer} uncompressed_data - The raw data to be compressed.
* @returns {Buffer} The compressed data.
*/
compress(uncompressed_data) {
this.clear();
if (typeof uncompressed_data === 'string') {
uncompressed_data = Buffer.from(uncompressed_data, 'utf8');
}
const uncompressed_len = uncompressed_data.length;
let i = 0;
let sum = 0;
let working_data = 0;
let flag = 0xFFFF;
let flags_index = 0;
let match_index = 0;
let type_index = 0;
while (i < uncompressed_len) {
let code_length = -1;
let code = -1;
const byte = uncompressed_data[i];
this.context.ring_buffer[i & 0x1FFF] = byte;
if (match_index > 0) {
// We are currently in a match sequence
if (byte !== this.context.ring_buffer[flag] || match_index > 0x0127) {
// Match ended, encode the match length
code_length = this.tables.matchEncode[match_index][1];
code = this.tables.matchEncode[match_index][0];
match_index = 0;
type_index = 3;
} else {
// Match continues
match_index += 1;
flag = (flag + 1) & 0x1FFF;
sum += byte;
working_data = ((working_data * 0x0100) + byte) | 0; // Keep as 32-bit int
i += 1;
}
} else {
// Not in a match sequence, look for one
flag = 0xFFFF;
if (i >= 3) {
// Calculate hash to find a potential match in the flag table
flags_index = ((working_data >>> 0x0B) ^ working_data) & 0x0FFF;
flag = this.context.flag_table[flags_index];
this.context.flag_table[flags_index] = i & 0x1FFF;
} else {
type_index += 1;
}
if (flag === 0xFFFF) {
// No match found, encode as a literal
code_length = this.tables.nomatchEncode[byte][1];
code = this.tables.nomatchEncode[byte][0] << 0x10;
} else if (byte === this.context.ring_buffer[flag]) {
// A match has started
match_index = 1;
flag = (flag + 1) & 0x1FFF;
type_index = 4;
} else {
// No match found, encode as a literal
code_length = this.tables.nomatchEncode[byte][1] + 1;
code = this.tables.nomatchEncode[byte][0] << 0x0F;
}
sum += byte;
working_data = ((working_data * 0x0100) + byte) | 0; // Keep as 32-bit int
i += 1;
}
if (code_length > 0) {
this.encodeLiteral(code_length, code);
}
}
// Finalize the stream
if (type_index === 2) {
this.encodeLiteral(0x10, 0x00990000);
} else if (type_index >= 3) {
if (type_index === 4) {
const code_length = this.tables.matchEncode[match_index][1];
const code = this.tables.matchEncode[match_index][0];
this.encodeLiteral(code_length, code);
}
flags_index = ((working_data >>> 0x0B) ^ working_data) & 0x0FFF;
flag = this.context.flag_table[flags_index];
if (flag === 0xFFFF) {
this.encodeLiteral(0x10, 0x00990000);
} else {
this.encodeLiteral(0x11, 0x004c8000);
}
}
// Encode checksum
sum &= 0xFFFF;
this.encodeLiteral(0x08, (sum << 0x10));
this.encodeLiteral(0x08, (sum << 0x18));
// Write any remaining bits and the filler byte
if (this.context.current_length !== 0) {
this.context.compressed_data.push(this.context.current_literal >>> 0x18);
}
this.context.compressed_data.push(this.context.filler_byte);
return Buffer.from(this.context.compressed_data);
}
/**
* Expands data compressed with the LZPF algorithm.
* @param {Buffer} compressed_data - The compressed data.
* @returns {Buffer} The expanded (original) data.
*/
expand(compressed_data) {
this.clear();
const uncompressed_data = [];
let block = 0;
let flags = 0xFFFF;
let iii = 0; // uncompressed data index
for (let i = 0; i < compressed_data.length; i++) {
const byte = compressed_data[i];
this.context.current_literal |= (byte << (0x18 - (this.context.current_length & 0x1f)));
this.context.current_length += 8;
while (this.context.current_length >= 0x13) {
// Check the high bit to determine if it's a match or a literal
if ((this.context.current_literal & 0x80000000) !== 0 && flags !== 0xFFFF) {
// It's a match
const match = this.readMatch();
let ring_buffer_index = flags;
for (let ii = 0; ii < match; ii++) {
const matched_byte = this.context.ring_buffer[ring_buffer_index & 0x1FFF];
uncompressed_data.push(matched_byte);
this.context.ring_buffer[iii & 0x1FFF] = matched_byte;
iii++;
block = ((block << 8) | matched_byte) | 0;
ring_buffer_index++;
}
} else {
// It's a literal, decode it
let byte = 0;
if (flags === 0xFFFF) {
byte = this.huffDecodeLiteralNoFlags();
} else {
byte = this.huffDecodeLiteral();
}
if (byte !== null) {
uncompressed_data.push(byte);
this.context.ring_buffer[iii & 0x1FFF] = byte;
iii++;
block = ((block << 8) | byte) | 0;
} else {
// End of stream marker found
return Buffer.from(uncompressed_data);
}
}
if (uncompressed_data.length >= 3) {
const flags_index = ((block >>> 0x0B) ^ block) & 0xFFF;
flags = this.context.flag_table[flags_index];
this.context.flag_table[flags_index] = uncompressed_data.length;
if (uncompressed_data.length === 3) {
break;
}
}
}
}
return Buffer.from(uncompressed_data);
}
/**
* Reads and decodes a match length from the bitstream.
* @returns {number} The length of the match.
*/
readMatch() {
let match = 0;
const width_index = (this.context.current_literal >>> 0x19) & 0x3E;
let data_width = this.tables.matchDecode[width_index + 1];
if (data_width === 0) {
if ((this.context.current_literal & 0x3e00000) === 0x3e00000) {
match = ((this.context.current_literal >>> 0x0D) & 0xFF) + 0x2A;
data_width = 0x13;
} else {
match = ((this.context.current_literal & 0x3e00000) >>> 0x15) + 0x0B;
data_width = 0x0B;
}
} else {
match = this.tables.matchDecode[width_index];
}
this.context.current_literal <<= (data_width & 0x1F);
this.context.current_length -= data_width;
return match;
}
/**
* Decodes a Huffman-encoded literal from the bitstream (when no flags are set).
* @returns {number|null} The decoded byte, or null if end of stream.
*/
huffDecodeLiteralNoFlags() {
let code = 0;
let data_width = this.tables.peekTable[this.context.current_literal >>> 0x17];
if (data_width === 0) {
code = this.context.current_literal >>> 0x16;
data_width = 0x0A;
while (code < this.tables.parents[data_width]) {
data_width++;
code = this.context.current_literal >>> (0x20 - (data_width & 0x1F));
}
} else {
code = (this.context.current_literal & this.tables.bitMask[data_width]) >>> (0x20 - (data_width & 0x1F));
}
this.context.current_literal <<= (data_width & 0x1F);
this.context.current_length -= data_width;
if (code === 0x99 && data_width === 0x10) {
return null; // End of stream marker
} else {
return this.tables.huff_lits[(code + this.tables.lit_base[data_width])];
}
}
/**
* Decodes a Huffman-encoded literal from the bitstream (when flags are set).
* @returns {number|null} The decoded byte, or null if end of stream.
*/
huffDecodeLiteral() {
let code = 0;
let data_width = this.tables.peekTable[this.context.current_literal >>> 0x16];
if (data_width === 0) {
code = this.context.current_literal >>> 0x15;
data_width = 0x0A;
while (code < this.tables.parents[data_width]) {
data_width++;
code = this.context.current_literal >>> (0x1F - (data_width & 0x1F));
}
} else {
code = (this.context.current_literal & this.tables.bitMask[(data_width + 1)]) >>> (0x1F - (data_width & 0x1F));
}
this.context.current_literal <<= ((data_width + 1) & 0x1F);
this.context.current_length -= (data_width + 1);
if (code === 0x99 && data_width === 0x10) {
return null; // End of stream marker
} else {
return this.tables.huff_lits[(code + this.tables.lit_base[data_width])];
}
}
}
module.exports = LZPF;