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/dist/Explode.js +1 -1
- package/dist/Explode.js.map +1 -1
- package/dist/Implode.d.ts +0 -1
- package/dist/Implode.js +1 -1
- package/dist/Implode.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/ExpandingBuffer.ts +0 -148
- package/src/Explode.ts +0 -404
- package/src/Implode.ts +0 -368
- package/src/bin/explode.ts +0 -80
- package/src/bin/helpers.ts +0 -65
- package/src/bin/implode.ts +0 -116
- package/src/constants.ts +0 -120
- package/src/errors.ts +0 -47
- package/src/functions.ts +0 -73
- package/src/index.ts +0 -30
- package/src/stream.ts +0 -220
- package/src/types.ts +0 -26
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
|
-
}
|
package/src/bin/explode.ts
DELETED
|
@@ -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
|
-
})()
|
package/src/bin/helpers.ts
DELETED
|
@@ -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
|
-
}
|
package/src/bin/implode.ts
DELETED
|
@@ -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
|
-
})()
|