···11-// @ts-check
22-33-/*!
44-MIT License
55-66-Copyright (c) 2021 Sebastiaan Marynissen
77-88-Permission is hereby granted, free of charge, to any person obtaining a copy
99-of this software and associated documentation files (the "Software"), to deal
1010-in the Software without restriction, including without limitation the rights
1111-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1212-copies of the Software, and to permit persons to whom the Software is
1313-furnished to do so, subject to the following conditions:
1414-1515-The above copyright notice and this permission notice shall be included in all
1616-copies or substantial portions of the Software.
1717-1818-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1919-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2020-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2121-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2222-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2323-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2424-SOFTWARE.
2525-*/
2626-2727-const qfs = (() => {
2828-2929- // # index.js
3030- // A JavaScript implementation of the QFS compression and decompression
3131- // algorithms. Based on wouanagaine's C library found here: https://github.com/
3232- // wouanagaine/SC4Mapper-2013/blob/master/Modules/qfs.c
3333-3434- // # decompress(input)
3535- // JavaScript implementation of the QFS decompression algorithm.
3636- // IMPORTANT! In some cases, the first 4 bytes indicate the size of the input
3737- // buffer. We **don't** detect this automatically, you need to discard those 4
3838- // bytes yourself!
3939-4040- /**
4141- * @param {Uint8Array} input
4242- * @returns {Uint8Array}
4343- */
4444- function decompress(input) {
4545-4646- // Check magic number.
4747- let [a, b] = input;
4848- if (!((a === 0x10 || a === 0x11) && b === 0xfb)) {
4949- throw new Error(
5050- 'Input is not a valid QFS compressed buffer! Did you forget to truncate the size bytes?'
5151- );
5252- }
5353-5454- // Create an malloc function based on the input buffer class we received.
5555- const malloc = createMalloc(input);
5656-5757- // First two bytes are 0x10fb (QFS id), then follows the *uncompressed*
5858- // size, which allows us to prepare a buffer for it.
5959- const size = 0x10000*input[2] + 0x100*input[3] + input[4];
6060- const out = malloc(size);
6161-6262- // Start decoding now. Note that trailing bytes are handled separately,
6363- // indicated by a control character >= 0xfc.
6464- let inpos = input[0] & 0x01 ? 8 : 5;
6565- let outpos = 0;
6666- while (inpos < input.length && input[inpos] < 0xfc) {
6767- let code = input[inpos];
6868- let a = input[inpos+1];
6969- let b = input[inpos+2];
7070- if (!(code & 0x80)) {
7171- let length = code & 3;
7272- memcpy(out, outpos, input, inpos+2, length);
7373- inpos += length+2;
7474- outpos += length;
7575-7676- // Repeat data that is already in the output. This is the essence
7777- // of the compression algorithm.
7878- length = ((code & 0x1c) >> 2) + 3;
7979- let offset = ((code >> 5) << 8) + a + 1;
8080- memcpy(out, outpos, out, outpos-offset, length);
8181- outpos += length;
8282-8383- } else if (!(code & 0x40)) {
8484- let length = (a >> 6) & 3;
8585- memcpy(out, outpos, input, inpos+3, length);
8686- inpos += length+3;
8787- outpos += length;
8888-8989- // Repeat data already in the outpot.
9090- length = (code & 0x3f) + 4;
9191- let offset = (a & 0x3f)*256 + b + 1;
9292- memcpy(out, outpos, out, outpos-offset, length);
9393- outpos += length;
9494-9595- } else if (!(code & 0x20)) {
9696- let c = input[inpos+3];
9797- let length = code & 3;
9898- memcpy(out, outpos, input, inpos+4, length);
9999- inpos += length+4;
100100- outpos += length;
101101-102102- // Repeat data that is already in the output.
103103- length = ((code>>2) & 3)*256 + c + 5;
104104- let offset = ((code & 0x10)<<12)+256*a + b + 1;
105105- memcpy(out, outpos, out, outpos-offset, length);
106106- outpos += length;
107107-108108- } else {
109109-110110- // The last case means there's no compression really, we just copy
111111- // as is.
112112- let length = (code & 0x1f)*4 + 4;
113113- memcpy(out, outpos, input, inpos+1, length);
114114- inpos += length+1;
115115- outpos += length;
116116-117117- }
118118-119119- }
120120-121121- // Trailing bytes. This is indicated by the control character being
122122- // greater than 0xfc.
123123- if (inpos < input.length && outpos < out.length) {
124124- let length = input[inpos] & 3;
125125- memcpy(out, outpos, input, inpos+1, length);
126126- outpos += length;
127127- }
128128-129129- // Check if everything is correct.
130130- if (outpos !== out.length) {
131131- throw new Error('Error when decompressing!');
132132- }
133133-134134- // We're done!
135135- return out;
136136-137137- }
138138-139139- // # createMalloc(buffer)
140140- // Returns an `malloc()` function which reuses the constructor of the given
141141- // buffer. That way, if we receive an Uint8Array, we output one as well and
142142- // vice versa: if we receive a Node.js buffer - even in the browser - we return
143143- // one.
144144- /**
145145- * @param {Uint8Array} buffer
146146- * @returns {(size: number) => Uint8Array}
147147- */
148148- function createMalloc(buffer) {
149149- const Ctor = buffer.constructor;
150150- // @ts-expect-error
151151- const Constructor = Ctor[Symbol.species] || Ctor;
152152- return size => new Constructor(size);
153153- }
154154-155155- // # memcpy(out, outpos, input, inpos, length)
156156- // LZ-compatible memcopy function. We don't use buffer.copy here because we
157157- // might be copying from ourselves as well!
158158- /**
159159- * @param {Uint8Array} out
160160- * @param {number} outpos
161161- * @param {Uint8Array} input
162162- * @param {number} inpos
163163- * @param {number} length
164164- */
165165- function memcpy(out, outpos, input, inpos, length) {
166166- let i = length;
167167- while (i--) {
168168- out[outpos++] = input[inpos++];
169169- }
170170- }
171171-172172- // # SmartBuffer
173173- // Tiny implementation of a smart buffer that only supports writing raw
174174- // *bytes*.
175175- const DEFAULT_SIZE = 4096;
176176- const MAX_SIZE = 32*1024*1024;
177177- class SmartBuffer {
178178- /**
179179- * @param {{ (size: number): Uint8Array; }} malloc
180180- */
181181- constructor(malloc) {
182182- this.length = 0;
183183- this.buffer = malloc(DEFAULT_SIZE);
184184- this.malloc = malloc;
185185- }
186186- /**
187187- * @param {number} byte
188188- */
189189- push(byte) {
190190- let { buffer } = this;
191191- if (buffer.length < this.length+1) {
192192- let newLength = Math.min(MAX_SIZE, 2*buffer.length);
193193- let newBuffer = this.malloc(newLength);
194194- newBuffer.set(buffer);
195195- this.buffer = newBuffer;
196196- }
197197- this.buffer[this.length++] = byte;
198198- }
199199- toBuffer() {
200200- return this.buffer.subarray(0, this.length);
201201- }
202202- }
203203-204204- // Performance calibration constants for compression.
205205- const QFS_MAXITER = 50;
206206-207207- // # compress(input, opts)
208208- // A JavaScript implementation of QFS compression. We use a smart buffer here
209209- // so that we don't have to manage the output size manually.
210210- /**
211211- * @param {Uint8Array} input
212212- * @param {{ windowBits?: number, includeSize?: boolean }} [opts]
213213- * @returns {Uint8Array}
214214- */
215215- function compress(input, opts = {}) {
216216-217217- // Important! If the input buffer is larger than 16MB, we can't compress
218218- // because that would cause a bit overflow and the size to be stored as 0!
219219- const inlen = input.length;
220220- if (inlen > 0xffffff) {
221221- throw new Error(`Input size cannot be larger than ${0xffffff} bytes!`);
222222- }
223223-224224- // Constants for tuning performance.
225225- const { windowBits = 17, includeSize = false } = opts;
226226- const WINDOW_LEN = 2**windowBits;
227227- const WINDOW_MASK = WINDOW_LEN-1;
228228-229229- // Prepare our buffer to which we'll write the output.
230230- const malloc = createMalloc(input);
231231- const out = new SmartBuffer(malloc);
232232- const push = out.push.bind(out);
233233-234234- // Initialize our occurence tables. The C++ code is rather difficult to
235235- // understand here as there is a lot of pointer magic involved.Anyway,
236236- // `rev_similar` is an array where we store the offsets that we calculated
237237- // every input position.
238238- let rev_similar = new Int32Array(WINDOW_LEN).fill(-1);
239239-240240- // The `rev_last` code is a lot more difficult to understand though. In
241241- // C++ it's a data structure that can hold 256 x 256 integer pointers.
242242- // This is actually a table for tracking the *offset* at which the last
243243- // [a, b] byte
244244- // sequence was found! We implement this table simply as a flat array. of
245245- // 256*256 size, which means our indices have to be calculated as 256*a +
246246- // b.
247247- let rev_last = new Int32Array(256*256).fill(-1);
248248-249249- // The "fill" method simply writes uncompressed data to the output stream.
250250- // We always do this right before writing away a "best length" match.
251251- let inpos = 0;
252252- let lastwrot = 0;
253253- const fill = () => {
254254- while (inpos - lastwrot >= 4) {
255255- let length = Math.floor((inpos - lastwrot)/4) - 1;
256256- if (length > 0x1b) length = 0x1b;
257257- push(0xe0 + length);
258258- length = 4*length + 4;
259259- while (length--) push(input[lastwrot++]);
260260- }
261261- };
262262-263263- // If we have to include the size of the compressed buffer as well, we'll
264264- // reserve 4 bytes to write this away once we know the size.
265265- if (includeSize) {
266266- for (let i = 0; i < 4; i++) push(0);
267267- }
268268-269269- // Write the header to the output.
270270- push(0x10);
271271- push(0xfb);
272272- push(inlen >> 16);
273273- push((inlen >> 8) & 0xff);
274274- push(inlen & 0xff);
275275-276276- // Main encoding loop.
277277- const max = inlen-1;
278278- for (; inpos < max; inpos++) {
279279-280280- // Update the occurence tables. The C++ code uses some pointer magic
281281- // for this, but we will do it in a more modern way. We simply update
282282- // the last time this combination was found.
283283- let index = 256*input[inpos] + input[inpos+1];
284284- let offs = rev_similar[inpos & WINDOW_MASK] = rev_last[index];
285285- rev_last[index] = inpos;
286286-287287- // If this part has already been compressed, skip ahead.
288288- if (inpos < lastwrot) continue;
289289-290290- // Look for a redundancy now.
291291- let bestlen = 0;
292292- let bestoffs = 0;
293293- let i = 0;
294294- while (offs >= 0 && inpos-offs < WINDOW_LEN && i++ < QFS_MAXITER) {
295295- let length = 2;
296296- let incmp = inpos + 2;
297297- let inref = offs + 2;
298298- while (
299299- incmp < inlen &&
300300- inref < inlen &&
301301- input[incmp++] === input[inref++] &&
302302- length < 1028
303303- ) {
304304- length++;
305305- }
306306- if (length > bestlen) {
307307- bestlen = length;
308308- bestoffs = inpos-offs;
309309- }
310310- offs = rev_similar[offs & WINDOW_MASK];
311311- }
312312-313313- // Check if redundancy is good enough.
314314- if (bestlen > inlen-inpos) {
315315- bestlen = inpos-inlen;
316316- } else if (
317317- bestlen <= 2 ||
318318- (bestlen === 3 && bestoffs > 1024) ||
319319- (bestlen === 4 && bestoffs > 16384)
320320- ) {
321321- continue;
322322- }
323323-324324- // If we did not find a suitable redundancy length by now, continue.
325325- // We do this to avoid additional nesting.
326326- if (!bestlen) continue;
327327-328328- // Cool, we found a good redundancy. Now write away.
329329- fill();
330330- let length = inpos-lastwrot;
331331- if (bestlen <= 10 && bestoffs <= 1024) {
332332-333333- // 2-byte control character.
334334- let d = bestoffs-1;
335335- push(((d>>8)<<5) + ((bestlen-3)<<2) + length);
336336- push(d & 0xff);
337337- while (length--) push(input[lastwrot++]);
338338- lastwrot += bestlen;
339339-340340- } else if (bestlen <= 67 && bestoffs <= 16384) {
341341-342342- // 3-byte control character.
343343- let d = bestoffs-1;
344344- push(0x80 + (bestlen-4));
345345- push((length<<6) + (d>>8));
346346- push(d & 0xff);
347347- while (length--) push(input[lastwrot++]);
348348- lastwrot += bestlen;
349349-350350- } else if (bestlen <= 1028 && bestoffs < WINDOW_LEN) {
351351-352352- // 4-byte control character.
353353- let d = bestoffs-1;
354354- push(0xC0 + ((d>>16)<<4) + (((bestlen-5)>>8)<<2) + length);
355355- push((d>>8) & 0xff);
356356- push(d & 0xff);
357357- push((bestlen-5) & 0xff);
358358- while (length--) push(input[lastwrot++]);
359359- lastwrot += bestlen;
360360-361361- }
362362-363363- }
364364-365365- // Grab the length of what still needs to be processed and write it away
366366- // as a control character. Then, write the raw contents.
367367- inpos = inlen;
368368- fill();
369369- let length = inpos - lastwrot;
370370- push(0xfc + length);
371371- while (length--) push(input[lastwrot++]);
372372-373373- // If we have to include the size, of the *compressed* buffer, do that as
374374- // well.
375375- let buffer = out.toBuffer();
376376- if (includeSize) {
377377- let size = out.length - 4;
378378- buffer[0] = size & 0xff;
379379- buffer[1] = (size >> 8) & 0xff;
380380- buffer[2] = (size >> 16) & 0xff;
381381- buffer[3] = (size >> 24) & 0xff;
382382- }
383383-384384- // We're done!
385385- return buffer;
386386-387387- }
388388-389389- return { decompress, compress };
390390-})();