node-pkware 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Implode.ts DELETED
@@ -1,368 +0,0 @@
1
- import { Buffer } from 'node:buffer'
2
- import { Transform, TransformCallback } from 'node:stream'
3
- import {
4
- ChBitsAsc,
5
- ChCodeAsc,
6
- Compression,
7
- DictionarySize,
8
- DistBits,
9
- DistCode,
10
- ExLenBits,
11
- LenBits,
12
- LenCode,
13
- LONGEST_ALLOWED_REPETITION,
14
- } from './constants'
15
- import { InvalidCompressionTypeError, InvalidDictionarySizeError } from './errors'
16
- import { ExpandingBuffer } from './ExpandingBuffer'
17
- import { clamp, clone, evenAndRemainder, getLowestNBits, last, nBitsOfOnes, repeat, toHex } from './functions'
18
- import { Config, Stats } from './types'
19
-
20
- export const getSizeOfMatching = (inputBytes: Buffer, a: number, b: number) => {
21
- const limit = clamp(2, LONGEST_ALLOWED_REPETITION, b - a)
22
-
23
- for (let i = 2; i <= limit; i++) {
24
- if (inputBytes[a + i] !== inputBytes[b + i]) {
25
- return i
26
- }
27
- }
28
-
29
- return limit
30
- }
31
-
32
- /**
33
- * TODO: make sure that we find the most recent one,
34
- * which in turn allows us to store backward length in less amount of bits
35
- * currently the code goes from the furthest point
36
- */
37
- const findRepetitions = (inputBytes: Buffer, endOfLastMatch: number, cursor: number) => {
38
- const notEnoughBytes = inputBytes.length - cursor < 2
39
- const tooClose = cursor === endOfLastMatch || cursor - endOfLastMatch < 2
40
- if (notEnoughBytes || tooClose) {
41
- return { size: 0, distance: 0 }
42
- }
43
-
44
- const haystack = inputBytes.subarray(endOfLastMatch, cursor)
45
- const needle = inputBytes.subarray(cursor, cursor + 2)
46
-
47
- const matchIndex = haystack.indexOf(needle)
48
- if (matchIndex !== -1) {
49
- const distance = cursor - endOfLastMatch - matchIndex
50
- return {
51
- distance: distance - 1,
52
- size: distance > 2 ? getSizeOfMatching(inputBytes, endOfLastMatch + matchIndex, cursor) : 2,
53
- }
54
- }
55
-
56
- return { size: 0, distance: 0 }
57
- }
58
-
59
- export class Implode {
60
- #verbose: boolean
61
- #isFirstChunk: boolean = true
62
- #inputBuffer: ExpandingBuffer
63
- #outputBuffer: ExpandingBuffer
64
- #stats: Stats = { chunkCounter: 0 }
65
- #compressionType: Compression = Compression.Unknown
66
- #dictionarySize: DictionarySize = DictionarySize.Unknown
67
- #dictionarySizeMask: number = -1
68
- #streamEnded: boolean = false
69
- #distCodes: number[] = clone(DistCode)
70
- #distBits: number[] = clone(DistBits)
71
- #startIndex: number = 0
72
- #handledFirstTwoBytes: boolean = false
73
- #outBits: number = 0
74
- #nChBits: number[] = repeat(0, 0x306)
75
- #nChCodes: number[] = repeat(0, 0x306)
76
-
77
- constructor(compressionType: Compression, dictionarySize: DictionarySize, config: Config) {
78
- if (!(compressionType in Compression) || compressionType === Compression.Unknown) {
79
- throw new InvalidCompressionTypeError()
80
- }
81
-
82
- if (!(dictionarySize in DictionarySize) || dictionarySize === DictionarySize.Unknown) {
83
- throw new InvalidDictionarySizeError()
84
- }
85
-
86
- this.#compressionType = compressionType
87
- this.#dictionarySize = dictionarySize
88
- this.#verbose = config?.verbose ?? false
89
- this.#inputBuffer = new ExpandingBuffer(config?.inputBufferSize ?? 0)
90
- this.#outputBuffer = new ExpandingBuffer(config?.outputBufferSize ?? 0)
91
- }
92
-
93
- getHandler() {
94
- const instance = this
95
-
96
- return function (this: Transform, chunk: Buffer, encoding: BufferEncoding, callback: TransformCallback) {
97
- try {
98
- instance.#inputBuffer.append(chunk)
99
-
100
- if (instance.#isFirstChunk) {
101
- instance.#isFirstChunk = false
102
- this._flush = instance.#onInputFinished.bind(instance)
103
- }
104
-
105
- if (instance.#verbose) {
106
- instance.#stats.chunkCounter++
107
- console.log(`implode: reading ${toHex(chunk.length)} bytes from chunk #${instance.#stats.chunkCounter}`)
108
- }
109
-
110
- instance.#processChunkData()
111
-
112
- const blockSize = 0x800
113
-
114
- if (instance.#outputBuffer.size() <= blockSize) {
115
- callback(null, Buffer.from([]))
116
- return
117
- }
118
-
119
- let [numberOfBlocks] = evenAndRemainder(instance.#outputBuffer.size(), blockSize)
120
-
121
- // making sure to leave one block worth of data for lookback when processing chunk data
122
- numberOfBlocks--
123
-
124
- const numberOfBytes = numberOfBlocks * blockSize
125
- // make sure to create a copy of the output buffer slice as it will get flushed in the next line
126
- const output = Buffer.from(instance.#outputBuffer.read(0, numberOfBytes))
127
- instance.#outputBuffer.flushStart(numberOfBytes)
128
-
129
- if (instance.#outBits === 0) {
130
- // set last byte to 0
131
- instance.#outputBuffer.dropEnd(1)
132
- instance.#outputBuffer.append(Buffer.from([0]))
133
- }
134
-
135
- callback(null, output)
136
- } catch (e: unknown) {
137
- callback(e as Error)
138
- }
139
- }
140
- }
141
-
142
- #onInputFinished(callback: TransformCallback) {
143
- this.#streamEnded = true
144
-
145
- try {
146
- this.#processChunkData()
147
-
148
- if (this.#verbose) {
149
- console.log('---------------')
150
- console.log('implode: total number of chunks read:', this.#stats.chunkCounter)
151
- console.log('implode: inputBuffer heap size', toHex(this.#inputBuffer.heapSize()))
152
- console.log('implode: outputBuffer heap size', toHex(this.#outputBuffer.heapSize()))
153
- }
154
-
155
- callback(null, this.#outputBuffer.read())
156
- } catch (e: unknown) {
157
- callback(e as Error)
158
- }
159
- }
160
-
161
- #processChunkData() {
162
- if (this.#dictionarySizeMask === -1) {
163
- this.#setup()
164
- }
165
-
166
- if (!this.#inputBuffer.isEmpty()) {
167
- this.#startIndex = 0
168
-
169
- if (!this.#handledFirstTwoBytes) {
170
- if (this.#inputBuffer.size() < 3) {
171
- return
172
- }
173
-
174
- this.#handledFirstTwoBytes = true
175
-
176
- this.#handleFirstTwoBytes()
177
- }
178
-
179
- // -------------------------------
180
-
181
- let endOfLastMatch = 0 // used when searching for longer repetitions later
182
-
183
- while (this.#startIndex < this.#inputBuffer.size()) {
184
- let { size, distance } = findRepetitions(
185
- this.#inputBuffer.read(endOfLastMatch),
186
- endOfLastMatch,
187
- this.#startIndex,
188
- )
189
-
190
- let isFlushable = this.#isRepetitionFlushable(size, distance)
191
-
192
- if (isFlushable === false) {
193
- const byte = this.#inputBuffer.readByte(this.#startIndex)
194
- this.#outputBits(this.#nChBits[byte], this.#nChCodes[byte])
195
- this.#startIndex += 1
196
- } else {
197
- if (isFlushable === null) {
198
- /*
199
- // Try to find better repetition 1 byte later.
200
- // stormlib/implode.c L517
201
- let cursor = this.#startIndex
202
- let newSize = size
203
- let newDistance = distance
204
- let currentSize
205
- let currentDistance
206
- while (newSize <= currentSize && this.#isRepetitionFlushable(newSize, newDistance)) {
207
- currentSize = newSize
208
- currentDistance = newDistance
209
- const reps = findRepetitions(this.#inputBuffer.read(endOfLastMatch), endOfLastMatch, ++cursor)
210
- newSize = reps.size
211
- newDistance = reps.distance
212
- }
213
- size = newSize
214
- distance = currentDistance
215
- */
216
- }
217
-
218
- const byte = size + 0xfe
219
- this.#outputBits(this.#nChBits[byte], this.#nChCodes[byte])
220
- if (size === 2) {
221
- const byte = distance >> 2
222
- this.#outputBits(this.#distBits[byte], this.#distCodes[byte])
223
- this.#outputBits(2, distance & 3)
224
- } else {
225
- const byte = distance >> this.#dictionarySize
226
- this.#outputBits(this.#distBits[byte], this.#distCodes[byte])
227
- this.#outputBits(this.#dictionarySize, this.#dictionarySizeMask & distance)
228
- }
229
-
230
- this.#startIndex += size
231
- }
232
-
233
- /*
234
- this.#inputBuffer.dropStart(endOfLastMatch)
235
- this.#startIndex -= endOfLastMatch
236
- endOfLastMatch = 0
237
- */
238
-
239
- if (this.#dictionarySize === DictionarySize.Small && this.#startIndex >= 0x400) {
240
- this.#inputBuffer.dropStart(0x400)
241
- this.#startIndex -= 0x400
242
- } else if (this.#dictionarySize === DictionarySize.Medium && this.#startIndex >= 0x800) {
243
- this.#inputBuffer.dropStart(0x800)
244
- this.#startIndex -= 0x800
245
- } else if (this.#dictionarySize === DictionarySize.Large && this.#startIndex >= 0x1000) {
246
- this.#inputBuffer.dropStart(0x1000)
247
- this.#startIndex -= 0x1000
248
- }
249
- }
250
-
251
- // -------------------------------
252
-
253
- this.#inputBuffer.dropStart(this.#inputBuffer.size())
254
- }
255
-
256
- if (this.#streamEnded) {
257
- // Write the termination literal
258
- this.#outputBits(last(this.#nChBits), last(this.#nChCodes))
259
- }
260
- }
261
-
262
- /**
263
- * @returns false - non flushable
264
- * @returns true - flushable
265
- * @returns null - flushable, but there might be a better repetition
266
- */
267
- #isRepetitionFlushable(size: number, distance: number) {
268
- if (size === 0) {
269
- return false
270
- }
271
-
272
- // If we found repetition of 2 bytes, that is 0x100 or further back,
273
- // don't bother. Storing the distance of 0x100 bytes would actually
274
- // take more space than storing the 2 bytes as-is.
275
- if (size === 2 && distance >= 0x100) {
276
- return false
277
- }
278
-
279
- if (size >= 8 || this.#startIndex + 1 >= this.#inputBuffer.size()) {
280
- return true
281
- }
282
-
283
- return null
284
- }
285
-
286
- /**
287
- * repetitions are at least 2 bytes long,
288
- * so the initial 2 bytes can be moved to the output as is
289
- */
290
- #handleFirstTwoBytes() {
291
- const byte1 = this.#inputBuffer.readByte(0)
292
- const byte2 = this.#inputBuffer.readByte(1)
293
- this.#outputBits(this.#nChBits[byte1], this.#nChCodes[byte1])
294
- this.#outputBits(this.#nChBits[byte2], this.#nChCodes[byte2])
295
- this.#startIndex += 2
296
- }
297
-
298
- #setup() {
299
- switch (this.#dictionarySize) {
300
- case DictionarySize.Large:
301
- this.#dictionarySizeMask = nBitsOfOnes(6)
302
- break
303
- case DictionarySize.Medium:
304
- this.#dictionarySizeMask = nBitsOfOnes(5)
305
- break
306
- case DictionarySize.Small:
307
- this.#dictionarySizeMask = nBitsOfOnes(4)
308
- break
309
- }
310
-
311
- switch (this.#compressionType) {
312
- case Compression.Binary:
313
- let nChCode = 0
314
- for (let nCount = 0; nCount < 0x100; nCount++) {
315
- this.#nChBits[nCount] = 9
316
- this.#nChCodes[nCount] = nChCode
317
- nChCode = getLowestNBits(16, nChCode) + 2
318
- }
319
- break
320
- case Compression.Ascii:
321
- for (let nCount = 0; nCount < 0x100; nCount++) {
322
- this.#nChBits[nCount] = ChBitsAsc[nCount] + 1
323
- this.#nChCodes[nCount] = ChCodeAsc[nCount] * 2
324
- }
325
- break
326
- }
327
-
328
- let nCount = 0x100
329
-
330
- for (let i = 0; i < 0x10; i++) {
331
- for (let nCount2 = 0; nCount2 < 1 << ExLenBits[i]; nCount2++) {
332
- this.#nChBits[nCount] = ExLenBits[i] + LenBits[i] + 1
333
- this.#nChCodes[nCount] = (nCount2 << (LenBits[i] + 1)) | (LenCode[i] * 2) | 1
334
- nCount++
335
- }
336
- }
337
-
338
- this.#outputBuffer.append(Buffer.from([this.#compressionType, this.#dictionarySize, 0]))
339
- this.#outBits = 0
340
- }
341
-
342
- #outputBits(nBits: number, bitBuffer: number) {
343
- if (nBits > 8) {
344
- this.#outputBits(8, bitBuffer)
345
- bitBuffer = bitBuffer >> 8
346
- nBits = nBits - 8
347
- }
348
-
349
- const outBits = this.#outBits
350
-
351
- const lastBytes = this.#outputBuffer.readByte(this.#outputBuffer.size() - 1)
352
- this.#outputBuffer.dropEnd(1)
353
- this.#outputBuffer.append(Buffer.from([lastBytes | getLowestNBits(8, bitBuffer << outBits)]))
354
-
355
- this.#outBits = this.#outBits + nBits
356
-
357
- if (this.#outBits > 8) {
358
- bitBuffer = bitBuffer >> (8 - outBits)
359
- this.#outputBuffer.append(Buffer.from([getLowestNBits(8, bitBuffer)]))
360
- this.#outBits = getLowestNBits(3, this.#outBits)
361
- } else {
362
- this.#outBits = getLowestNBits(3, this.#outBits)
363
- if (this.#outBits === 0) {
364
- this.#outputBuffer.append(Buffer.from([0]))
365
- }
366
- }
367
- }
368
- }
@@ -1,80 +0,0 @@
1
- #!/usr/bin/env -S node --enable-source-maps
2
-
3
- import minimist from 'minimist-lite'
4
- import { getPackageVersion, parseNumberString, getInputStream, getOutputStream } from './helpers'
5
- import { transformEmpty, transformIdentity, transformSplitBy, splitAt, through } from '../stream'
6
- import { Config } from '../types'
7
- import { explode } from '../index'
8
-
9
- type AppArgs = {
10
- _: string[]
11
- output?: string
12
- offset?: string
13
- 'input-buffer-size'?: string
14
- 'output-buffer-size'?: string
15
- version: boolean
16
- 'drop-before-offset': boolean
17
- verbose: boolean
18
- v: boolean
19
- }
20
-
21
- const args: AppArgs = minimist(process.argv.slice(2), {
22
- string: ['output', 'offset', 'input-buffer-size', 'output-buffer-size'],
23
- boolean: ['version', 'drop-before-offset', 'verbose'],
24
- alias: {
25
- v: 'version',
26
- },
27
- })
28
-
29
- const decompress = (
30
- input: NodeJS.ReadableStream,
31
- output: NodeJS.WritableStream,
32
- offset: number,
33
- keepHeader: boolean,
34
- config: Config,
35
- ) => {
36
- const leftHandler = keepHeader ? transformIdentity() : transformEmpty()
37
- const rightHandler = explode(config)
38
-
39
- const handler = transformSplitBy(splitAt(offset), leftHandler, rightHandler)
40
-
41
- return new Promise((resolve, reject) => {
42
- input.pipe(through(handler).on('error', reject)).pipe(output).on('finish', resolve).on('error', reject)
43
- })
44
- }
45
-
46
- ;(async () => {
47
- if (args.version) {
48
- const version = await getPackageVersion()
49
- console.log(`node-pkware - version ${version}`)
50
- process.exit(0)
51
- }
52
-
53
- let input: NodeJS.ReadableStream
54
- let output: NodeJS.WritableStream
55
- try {
56
- input = await getInputStream(args._[0])
57
- output = await getOutputStream(args.output)
58
- } catch (e: unknown) {
59
- const error = e as Error
60
- console.error('error:', error.message)
61
- process.exit(1)
62
- }
63
-
64
- const offset = parseNumberString(args.offset, 0)
65
- const keepHeader = !args['drop-before-offset']
66
- const config: Config = {
67
- verbose: args.verbose,
68
- inputBufferSize: parseNumberString(args['input-buffer-size'], 0x10000),
69
- outputBufferSize: parseNumberString(args['output-buffer-size'], 0x40000),
70
- }
71
-
72
- decompress(input, output, offset, keepHeader, config)
73
- .then(() => {
74
- process.exit(0)
75
- })
76
- .catch((e) => {
77
- console.error(`error: ${e.message}`)
78
- process.exit(1)
79
- })
80
- })()
@@ -1,65 +0,0 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
-
4
- const isDecimalString = (input: string) => {
5
- return /^\d+$/.test(input)
6
- }
7
-
8
- const isFullHexString = (input: string) => {
9
- return /^\s*0x[0-9a-f]+\s*$/.test(input)
10
- }
11
-
12
- export const parseNumberString = (n?: string, defaultValue: number = 0) => {
13
- if (typeof n === 'undefined') {
14
- return defaultValue
15
- }
16
-
17
- if (isDecimalString(n)) {
18
- return parseInt(n)
19
- }
20
-
21
- if (isFullHexString(n)) {
22
- return parseInt(n.replace(/^0x/, ''), 16)
23
- }
24
-
25
- return defaultValue
26
- }
27
-
28
- export const getPackageVersion = async () => {
29
- try {
30
- const rawIn = await fs.promises.readFile(path.resolve(__dirname, '../../package.json'), 'utf-8')
31
- const { version } = JSON.parse(rawIn) as { version: string }
32
- return version
33
- } catch (error: unknown) {
34
- return 'unknown'
35
- }
36
- }
37
-
38
- const fileExists = async (filename: string) => {
39
- try {
40
- await fs.promises.access(filename, fs.constants.R_OK)
41
- return true
42
- } catch (error: unknown) {
43
- return false
44
- }
45
- }
46
-
47
- export const getInputStream = async (filename?: string): Promise<NodeJS.ReadableStream> => {
48
- if (typeof filename === 'undefined') {
49
- return process.openStdin()
50
- }
51
-
52
- if (await fileExists(filename)) {
53
- return fs.createReadStream(filename)
54
- }
55
-
56
- throw new Error('input file does not exist')
57
- }
58
-
59
- export const getOutputStream = async (filename?: string): Promise<NodeJS.WritableStream> => {
60
- if (typeof filename === 'undefined') {
61
- return process.stdout
62
- }
63
-
64
- return fs.createWriteStream(filename)
65
- }
@@ -1,116 +0,0 @@
1
- #!/usr/bin/env -S node --enable-source-maps
2
-
3
- import minimist from 'minimist-lite'
4
- import { Compression, DictionarySize } from '../constants'
5
- import { getPackageVersion, parseNumberString, getInputStream, getOutputStream } from './helpers'
6
- import { transformEmpty, transformIdentity, transformSplitBy, splitAt, through } from '../stream'
7
- import { Config } from '../types'
8
- import { implode } from '../index'
9
-
10
- type AppArgs = {
11
- _: string[]
12
- output?: string
13
- offset?: string
14
- 'input-buffer-size'?: string
15
- 'output-buffer-size'?: string
16
- version: boolean
17
- binary: boolean
18
- ascii: boolean
19
- 'drop-before-offset': boolean
20
- verbose: boolean
21
- small: boolean
22
- medium: boolean
23
- large: boolean
24
- v: boolean
25
- b: boolean
26
- a: boolean
27
- s: boolean
28
- m: boolean
29
- l: boolean
30
- }
31
-
32
- const args: AppArgs = minimist(process.argv.slice(2), {
33
- string: ['output', 'offset', 'input-buffer-size', 'output-buffer-size'],
34
- boolean: ['version', 'ascii', 'binary', 'small', 'medium', 'large', 'drop-before-offset', 'verbose'],
35
- alias: {
36
- v: 'version',
37
- a: 'ascii',
38
- b: 'binary',
39
- s: 'small',
40
- m: 'medium',
41
- l: 'large',
42
- },
43
- })
44
-
45
- const compress = (
46
- input: NodeJS.ReadableStream,
47
- output: NodeJS.WritableStream,
48
- offset: number,
49
- keepHeader: boolean,
50
- compressionType: Compression,
51
- dictionarySize: DictionarySize,
52
- config: Config,
53
- ) => {
54
- const leftHandler = keepHeader ? transformIdentity() : transformEmpty()
55
- const rightHandler = implode(compressionType, dictionarySize, config)
56
-
57
- const handler = transformSplitBy(splitAt(offset), leftHandler, rightHandler)
58
-
59
- return new Promise((resolve, reject) => {
60
- input.pipe(through(handler).on('error', reject)).pipe(output).on('finish', resolve).on('error', reject)
61
- })
62
- }
63
-
64
- ;(async () => {
65
- if (args.version) {
66
- const version = await getPackageVersion()
67
- console.log(`node-pkware - version ${version}`)
68
- process.exit(0)
69
- }
70
-
71
- let input: NodeJS.ReadableStream
72
- let output: NodeJS.WritableStream
73
- try {
74
- if (!args.ascii && !args.binary) {
75
- throw new Error('compression type missing, expected either --ascii or --binary')
76
- }
77
-
78
- if (args.ascii && args.binary) {
79
- throw new Error('multiple compression types specified, can only work with either --ascii or --binary')
80
- }
81
-
82
- if (!args.small && !args.medium && !args.large) {
83
- throw new Error('dictionary size missing, expected either --small, --medium or --large')
84
- }
85
-
86
- if ((args.small ? 1 : 0) + (args.medium ? 1 : 0) + (args.large ? 1 : 0) > 1) {
87
- throw new Error('multiple dictionary sizes specified, can only work with either --small, --medium or --large')
88
- }
89
-
90
- input = await getInputStream(args._[0])
91
- output = await getOutputStream(args.output)
92
- } catch (e: unknown) {
93
- const error = e as Error
94
- console.error('error:', error.message)
95
- process.exit(1)
96
- }
97
-
98
- const compressionType = args.ascii ? Compression.Ascii : Compression.Binary
99
- const dictionarySize = args.small ? DictionarySize.Small : args.medium ? DictionarySize.Medium : DictionarySize.Large
100
- const offset = parseNumberString(args.offset, 0)
101
- const keepHeader = !args['drop-before-offset']
102
- const config: Config = {
103
- verbose: args.verbose,
104
- inputBufferSize: parseNumberString(args['input-buffer-size'], 0x10000),
105
- outputBufferSize: parseNumberString(args['output-buffer-size'], 0x12000),
106
- }
107
-
108
- compress(input, output, offset, keepHeader, compressionType, dictionarySize, config)
109
- .then(() => {
110
- process.exit(0)
111
- })
112
- .catch((e) => {
113
- console.error(`error: ${e.message}`)
114
- process.exit(1)
115
- })
116
- })()