New WTVTellyScript class for detok and retok, ported from @wtvemac's disk_editor
This commit is contained in:
255
zefie_wtvp_minisrv/includes/classes/LZSS.js
Normal file
255
zefie_wtvp_minisrv/includes/classes/LZSS.js
Normal file
@@ -0,0 +1,255 @@
|
||||
class LZSS {
|
||||
constructor() {
|
||||
this.ring_buffer_size = 0x1000; // 4096 bytes
|
||||
this.root_index = 0x1000; // 4096
|
||||
this.max_match_length = 0x11; // 17 bytes
|
||||
this.match_threshold = 0x03; // minimum match length = 3
|
||||
this.match_position = 0;
|
||||
this.match_length = 0;
|
||||
this.clear();
|
||||
}
|
||||
|
||||
clear() {
|
||||
// Allocate ring buffer with extra room for lookahead.
|
||||
this.ring_buffer = new Uint8Array(this.ring_buffer_size + this.max_match_length);
|
||||
// Initialize helper arrays.
|
||||
this.parent = new Array(this.ring_buffer_size + 1).fill(0);
|
||||
this.rchild = new Array((this.ring_buffer_size * 2) + 1).fill(0);
|
||||
this.lchild = new Array((this.ring_buffer_size * 2) + 1).fill(0);
|
||||
|
||||
// Fill the first part of the ring buffer with spaces (0x20).
|
||||
for (let ii = 0; ii < (this.ring_buffer_size - this.max_match_length); ii++) {
|
||||
this.ring_buffer[ii] = 0x20;
|
||||
}
|
||||
// Initialize rchild for indices beyond the ring buffer.
|
||||
for (let ii = this.ring_buffer_size + 1; ii < this.rchild.length; ii++) {
|
||||
this.rchild[ii] = this.root_index;
|
||||
}
|
||||
// Set all parent pointers to the root index.
|
||||
for (let ii = 0; ii < (this.ring_buffer_size - 1); ii++) {
|
||||
this.parent[ii] = this.root_index;
|
||||
}
|
||||
this.match_position = 0;
|
||||
this.match_length = 0;
|
||||
}
|
||||
|
||||
insertNode(i) {
|
||||
let keyi = this.ring_buffer[i];
|
||||
let keyii = this.ring_buffer[i + 1] ^ this.ring_buffer[i + 2];
|
||||
keyii = ((keyii ^ (keyii >> 4)) & 0x0F) << 8;
|
||||
|
||||
let parent_index = i;
|
||||
let parent_link = (this.ring_buffer_size + 1) + keyi + keyii;
|
||||
let child_index = parent_link;
|
||||
let child_link = i;
|
||||
|
||||
// Initialize children for node i.
|
||||
this.rchild[i] = this.root_index;
|
||||
this.lchild[i] = this.root_index;
|
||||
this.match_length = 0;
|
||||
let matched_list = this.rchild;
|
||||
let cmp_index = 1;
|
||||
let looped = 0;
|
||||
while (true) {
|
||||
looped++;
|
||||
if (looped >= 0xFFFF) {
|
||||
throw new Error("Runaway loop in insertNode!");
|
||||
}
|
||||
if (cmp_index >= 0) {
|
||||
cmp_index = this.rchild[parent_link];
|
||||
matched_list = this.rchild;
|
||||
} else {
|
||||
cmp_index = this.lchild[parent_link];
|
||||
matched_list = this.lchild;
|
||||
}
|
||||
if (cmp_index === this.root_index) {
|
||||
parent_index = i;
|
||||
child_index = parent_link;
|
||||
child_link = i;
|
||||
break;
|
||||
}
|
||||
parent_link = cmp_index;
|
||||
let ii = 1;
|
||||
while (ii < this.max_match_length) {
|
||||
if (this.ring_buffer[i + ii] !== this.ring_buffer[parent_link + ii]) {
|
||||
break;
|
||||
}
|
||||
ii++;
|
||||
}
|
||||
if (ii > this.match_length) {
|
||||
this.match_length = ii;
|
||||
this.match_position = parent_link;
|
||||
if (ii > (this.max_match_length - 1)) {
|
||||
this.parent[i] = this.parent[parent_link];
|
||||
this.rchild[i] = this.rchild[parent_link];
|
||||
this.lchild[i] = this.lchild[parent_link];
|
||||
this.parent[this.rchild[i]] = i;
|
||||
this.parent[this.lchild[i]] = i;
|
||||
if (this.rchild[this.parent[parent_link]] !== parent_link) {
|
||||
matched_list = this.lchild;
|
||||
} else {
|
||||
matched_list = this.rchild;
|
||||
}
|
||||
child_index = this.parent[parent_link];
|
||||
child_link = i;
|
||||
parent_index = parent_link;
|
||||
parent_link = this.root_index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.parent[parent_index] = parent_link;
|
||||
matched_list[child_index] = child_link;
|
||||
}
|
||||
|
||||
deleteNode(i) {
|
||||
if (this.parent[i] !== this.root_index) {
|
||||
let ii = 0;
|
||||
if (this.rchild[i] === this.root_index) {
|
||||
ii = this.lchild[i];
|
||||
} else if (this.lchild[i] === this.root_index) {
|
||||
ii = this.rchild[i];
|
||||
} else {
|
||||
ii = this.lchild[i];
|
||||
if (ii !== this.root_index) {
|
||||
let looped = 0;
|
||||
while (ii !== this.root_index) {
|
||||
looped++;
|
||||
if (looped >= 0xFFFF) {
|
||||
throw new Error("Runaway loop in deleteNode!");
|
||||
}
|
||||
ii = this.rchild[ii];
|
||||
}
|
||||
this.rchild[this.parent[ii]] = this.lchild[ii];
|
||||
this.parent[this.lchild[ii]] = this.parent[ii];
|
||||
this.lchild[ii] = this.lchild[i];
|
||||
this.parent[this.lchild[i]] = ii;
|
||||
}
|
||||
this.rchild[ii] = this.rchild[i];
|
||||
this.parent[this.rchild[i]] = ii;
|
||||
}
|
||||
this.parent[ii] = this.parent[i];
|
||||
let parent_link = this.parent[i];
|
||||
if (this.rchild[parent_link] !== i) {
|
||||
this.lchild[parent_link] = ii;
|
||||
} else {
|
||||
this.rchild[parent_link] = ii;
|
||||
}
|
||||
this.parent[i] = this.root_index;
|
||||
}
|
||||
}
|
||||
|
||||
compress(uncompressedData) {
|
||||
const uncompressed_size = uncompressedData.length;
|
||||
let i = 0;
|
||||
let ring_index = 0;
|
||||
const ring_footer_start = this.ring_buffer_size - this.max_match_length - 1;
|
||||
let footer_index = ring_footer_start;
|
||||
let length = 0;
|
||||
|
||||
// Fill lookahead buffer.
|
||||
for (; length <= this.max_match_length && i < uncompressed_size; length++) {
|
||||
this.ring_buffer[ring_footer_start + length] = uncompressedData[i++];
|
||||
}
|
||||
|
||||
let mask = 1; // use as 8-bit flag
|
||||
const code_buffer = new Array(0x14).fill(0); // 20 bytes buffer; first byte holds flags.
|
||||
let code_buffer_index = 1;
|
||||
const compressed_data = [];
|
||||
|
||||
this.insertNode(ring_footer_start);
|
||||
while (length > 0) {
|
||||
if (this.match_length > length) {
|
||||
this.match_length = length;
|
||||
}
|
||||
|
||||
if (this.match_length >= this.match_threshold) {
|
||||
let _match_position = footer_index - this.match_position - 1;
|
||||
if (_match_position < 0) {
|
||||
_match_position += this.ring_buffer_size;
|
||||
}
|
||||
code_buffer[code_buffer_index++] = _match_position & 0xFF;
|
||||
code_buffer[code_buffer_index++] =
|
||||
(((_match_position >> 4) & 0xF0) | (this.match_length - this.match_threshold)) & 0xFF;
|
||||
} else {
|
||||
this.match_length = 1;
|
||||
code_buffer[0] |= mask;
|
||||
code_buffer[code_buffer_index++] = this.ring_buffer[footer_index];
|
||||
}
|
||||
|
||||
// Shift mask as an 8-bit value.
|
||||
mask = (mask << 1) & 0xFF;
|
||||
if (mask === 0) {
|
||||
for (let ii = 0; ii < code_buffer_index; ii++) {
|
||||
compressed_data.push(code_buffer[ii]);
|
||||
}
|
||||
code_buffer[0] = 0;
|
||||
mask = 1;
|
||||
code_buffer_index = 1;
|
||||
}
|
||||
|
||||
const last_match_length = this.match_length;
|
||||
if (last_match_length > 0) {
|
||||
for (let ii = 0; ii < last_match_length; ii++, i++) {
|
||||
this.deleteNode(ring_index);
|
||||
if (i < uncompressed_size) {
|
||||
this.ring_buffer[ring_index] = uncompressedData[i];
|
||||
if (ring_index <= (this.max_match_length - 1)) {
|
||||
this.ring_buffer[this.ring_buffer_size + ring_index] = uncompressedData[i];
|
||||
}
|
||||
} else {
|
||||
i = uncompressed_size - 1;
|
||||
length--;
|
||||
}
|
||||
ring_index = (ring_index + 1) & (this.ring_buffer_size - 1);
|
||||
footer_index = (footer_index + 1) & (this.ring_buffer_size - 1);
|
||||
if (length !== 0) {
|
||||
this.insertNode(footer_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (code_buffer_index > 1) {
|
||||
for (let ii = 0; ii < code_buffer_index; ii++) {
|
||||
compressed_data.push(code_buffer[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
return new Uint8Array(compressed_data);
|
||||
}
|
||||
|
||||
expand(compressedData, uncompressedSize = 0, flagsStart = 0) {
|
||||
const compressed_size = compressedData.length;
|
||||
const uncompressed_data = [];
|
||||
let flags = flagsStart >>> 0;
|
||||
let i = 0;
|
||||
while (i < compressed_size) {
|
||||
if ((flags & 0x100) === 0) {
|
||||
flags = compressedData[i] | 0xFF00;
|
||||
i++;
|
||||
}
|
||||
const current_byte = compressedData[i];
|
||||
if ((flags & 0x01) === 0x01) {
|
||||
uncompressed_data.push(current_byte);
|
||||
} else {
|
||||
i++;
|
||||
const next_byte = compressedData[i];
|
||||
const m = (((next_byte & 0xF0) << 4) | current_byte) >>> 0;
|
||||
const matchLen = (next_byte & 0x0F) + this.match_threshold;
|
||||
for (let ii = 0; ii < matchLen; ii++) {
|
||||
const index = uncompressed_data.length - (m + 1);
|
||||
uncompressed_data.push(uncompressed_data[index]);
|
||||
}
|
||||
}
|
||||
flags >>= 1;
|
||||
i++;
|
||||
if (uncompressedSize > 0 && uncompressed_data.length >= uncompressedSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new Uint8Array(uncompressed_data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LZSS;
|
||||
Reference in New Issue
Block a user