···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+})();