node-pkware 0.7.0 → 1.0.0-alpha.1
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/README.md +228 -82
- package/bin/explode.js +77 -0
- package/bin/implode.js +113 -0
- package/package.json +32 -29
- package/src/{constants.mjs → constants.js} +50 -26
- package/src/errors.js +50 -0
- package/src/explode.js +414 -0
- package/src/{QuasiImmutableBuffer.mjs → helpers/ExpandingBuffer.js} +14 -2
- package/src/helpers/functions.js +125 -0
- package/src/helpers/stream.js +190 -0
- package/src/helpers/testing.js +78 -0
- package/src/implode.js +365 -0
- package/src/index.js +18 -0
- package/types/constants.d.ts +41 -0
- package/types/errors.d.ts +30 -0
- package/types/explode.d.ts +56 -0
- package/types/helpers/ExpandingBuffer.d.ts +25 -0
- package/types/helpers/Shared.d.ts +46 -0
- package/types/helpers/functions.d.ts +10 -0
- package/types/helpers/stream.d.ts +61 -0
- package/types/helpers/testing.d.ts +6 -0
- package/types/implode.d.ts +63 -0
- package/types/index.d.ts +8 -0
- package/bin/explode.mjs +0 -97
- package/bin/helpers.mjs +0 -36
- package/bin/implode.mjs +0 -110
- package/notes-on-implode.txt +0 -130
- package/src/explode.mjs +0 -340
- package/src/helpers.mjs +0 -120
- package/src/implode.mjs +0 -220
- package/src/index.mjs +0 -25
|
@@ -1,28 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const COMPRESSION_BINARY = 0
|
|
2
|
+
const COMPRESSION_ASCII = 1
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export const ERROR_ABORTED = 'Aborted'
|
|
4
|
+
const DICTIONARY_SIZE_SMALL = 4
|
|
5
|
+
const DICTIONARY_SIZE_MEDIUM = 5
|
|
6
|
+
const DICTIONARY_SIZE_LARGE = 6
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
export const DICTIONARY_SIZE2 = 0x0800
|
|
11
|
-
export const DICTIONARY_SIZE3 = 0x1000
|
|
8
|
+
const LONGEST_ALLOWED_REPETITION = 0x204
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
const PKDCL_OK = 'OK'
|
|
11
|
+
const PKDCL_STREAM_END = 'All data from the input stream is read'
|
|
12
|
+
const PKDCL_NEED_DICT = 'Need more data (dictionary)'
|
|
13
|
+
const PKDCL_CONTINUE = 'Continue (internal flag)'
|
|
14
|
+
const PKDCL_GET_INPUT = 'Get input (internal flag)'
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export const PKDCL_NEED_DICT = 'Need more data (dictionary)'
|
|
18
|
-
export const PKDCL_CONTINUE = 'Continue (internal flag)'
|
|
19
|
-
export const PKDCL_GET_INPUT = 'Get input (internal flag)'
|
|
20
|
-
|
|
21
|
-
export const LITERAL_END_STREAM = 0x305
|
|
22
|
-
export const LITERAL_STREAM_ABORTED = 0x306
|
|
16
|
+
const LITERAL_END_STREAM = 0x305
|
|
17
|
+
const LITERAL_STREAM_ABORTED = 0x306
|
|
23
18
|
|
|
24
19
|
// prettier-ignore
|
|
25
|
-
|
|
20
|
+
const DistCode = [
|
|
26
21
|
0x03, 0x0D, 0x05, 0x19, 0x09, 0x11, 0x01, 0x3E, 0x1E, 0x2E, 0x0E, 0x36, 0x16, 0x26, 0x06, 0x3A,
|
|
27
22
|
0x1A, 0x2A, 0x0A, 0x32, 0x12, 0x22, 0x42, 0x02, 0x7C, 0x3C, 0x5C, 0x1C, 0x6C, 0x2C, 0x4C, 0x0C,
|
|
28
23
|
0x74, 0x34, 0x54, 0x14, 0x64, 0x24, 0x44, 0x04, 0x78, 0x38, 0x58, 0x18, 0x68, 0x28, 0x48, 0x08,
|
|
@@ -30,7 +25,7 @@ export const DistCode = [
|
|
|
30
25
|
]
|
|
31
26
|
|
|
32
27
|
// prettier-ignore
|
|
33
|
-
|
|
28
|
+
const DistBits = [
|
|
34
29
|
0x02, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
|
|
35
30
|
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
|
|
36
31
|
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
|
|
@@ -38,28 +33,28 @@ export const DistBits = [
|
|
|
38
33
|
]
|
|
39
34
|
|
|
40
35
|
// prettier-ignore
|
|
41
|
-
|
|
36
|
+
const LenBits = [
|
|
42
37
|
0x03, 0x02, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07
|
|
43
38
|
]
|
|
44
39
|
|
|
45
40
|
// prettier-ignore
|
|
46
|
-
|
|
41
|
+
const LenCode = [
|
|
47
42
|
0x05, 0x03, 0x01, 0x06, 0x0a, 0x02, 0x0c, 0x14, 0x04, 0x18, 0x08, 0x30, 0x10, 0x20, 0x40, 0x00
|
|
48
43
|
]
|
|
49
44
|
|
|
50
45
|
// prettier-ignore
|
|
51
|
-
|
|
46
|
+
const ExLenBits = [
|
|
52
47
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
|
|
53
48
|
]
|
|
54
49
|
|
|
55
50
|
// prettier-ignore
|
|
56
|
-
|
|
51
|
+
const LenBase = [
|
|
57
52
|
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
|
|
58
53
|
0x0008, 0x000A, 0x000E, 0x0016, 0x0026, 0x0046, 0x0086, 0x0106
|
|
59
54
|
]
|
|
60
55
|
|
|
61
56
|
// prettier-ignore
|
|
62
|
-
|
|
57
|
+
const ChBitsAsc = [
|
|
63
58
|
0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x08, 0x07, 0x0C, 0x0C, 0x07, 0x0C, 0x0C,
|
|
64
59
|
0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
|
|
65
60
|
0x04, 0x0A, 0x08, 0x0C, 0x0A, 0x0C, 0x0A, 0x08, 0x07, 0x07, 0x08, 0x09, 0x07, 0x06, 0x07, 0x08,
|
|
@@ -79,7 +74,7 @@ export const ChBitsAsc = [
|
|
|
79
74
|
]
|
|
80
75
|
|
|
81
76
|
// prettier-ignore
|
|
82
|
-
|
|
77
|
+
const ChCodeAsc = [
|
|
83
78
|
0x0490, 0x0FE0, 0x07E0, 0x0BE0, 0x03E0, 0x0DE0, 0x05E0, 0x09E0,
|
|
84
79
|
0x01E0, 0x00B8, 0x0062, 0x0EE0, 0x06E0, 0x0022, 0x0AE0, 0x02E0,
|
|
85
80
|
0x0CE0, 0x04E0, 0x08E0, 0x00E0, 0x0F60, 0x0760, 0x0B60, 0x0360,
|
|
@@ -113,3 +108,32 @@ export const ChCodeAsc = [
|
|
|
113
108
|
0x0600, 0x1A00, 0x0E40, 0x0640, 0x0A40, 0x0A00, 0x1200, 0x0200,
|
|
114
109
|
0x1C00, 0x0C00, 0x1400, 0x0400, 0x1800, 0x0800, 0x1000, 0x0000
|
|
115
110
|
]
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
COMPRESSION_BINARY,
|
|
114
|
+
COMPRESSION_ASCII,
|
|
115
|
+
|
|
116
|
+
DICTIONARY_SIZE_SMALL,
|
|
117
|
+
DICTIONARY_SIZE_MEDIUM,
|
|
118
|
+
DICTIONARY_SIZE_LARGE,
|
|
119
|
+
|
|
120
|
+
LONGEST_ALLOWED_REPETITION,
|
|
121
|
+
|
|
122
|
+
PKDCL_OK,
|
|
123
|
+
PKDCL_STREAM_END,
|
|
124
|
+
PKDCL_NEED_DICT,
|
|
125
|
+
PKDCL_CONTINUE,
|
|
126
|
+
PKDCL_GET_INPUT,
|
|
127
|
+
|
|
128
|
+
LITERAL_END_STREAM,
|
|
129
|
+
LITERAL_STREAM_ABORTED,
|
|
130
|
+
|
|
131
|
+
DistCode,
|
|
132
|
+
DistBits,
|
|
133
|
+
LenBits,
|
|
134
|
+
LenCode,
|
|
135
|
+
ExLenBits,
|
|
136
|
+
LenBase,
|
|
137
|
+
ChBitsAsc,
|
|
138
|
+
ChCodeAsc
|
|
139
|
+
}
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class InvalidDictionarySizeError extends Error {
|
|
2
|
+
constructor() {
|
|
3
|
+
super('Invalid dictionary size')
|
|
4
|
+
this.name = 'InvalidDictionarySizeError'
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class InvalidCompressionTypeError extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super('Invalid compression type')
|
|
11
|
+
this.name = 'InvalidCompressionTypeError'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class InvalidDataError extends Error {
|
|
16
|
+
constructor() {
|
|
17
|
+
super('Invalid data')
|
|
18
|
+
this.name = 'InvalidDataError'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class AbortedError extends Error {
|
|
23
|
+
constructor() {
|
|
24
|
+
super('Aborted')
|
|
25
|
+
this.name = 'AbortedError'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class ExpectedBufferError extends TypeError {
|
|
30
|
+
constructor() {
|
|
31
|
+
super('Expected variable to be of type Buffer')
|
|
32
|
+
this.name = 'ExpectedBufferError'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class ExpectedFunctionError extends TypeError {
|
|
37
|
+
constructor() {
|
|
38
|
+
super('Expected variable to be a Function')
|
|
39
|
+
this.name = 'ExpectedFunctionError'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
InvalidDictionarySizeError,
|
|
45
|
+
InvalidCompressionTypeError,
|
|
46
|
+
InvalidDataError,
|
|
47
|
+
AbortedError,
|
|
48
|
+
ExpectedBufferError,
|
|
49
|
+
ExpectedFunctionError
|
|
50
|
+
}
|
package/src/explode.js
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
const { repeat, unfold, reduce, has, includes } = require('ramda')
|
|
2
|
+
const { isFunction } = require('ramda-adjunct')
|
|
3
|
+
const {
|
|
4
|
+
InvalidDataError,
|
|
5
|
+
InvalidCompressionTypeError,
|
|
6
|
+
InvalidDictionarySizeError,
|
|
7
|
+
ExpectedBufferError,
|
|
8
|
+
ExpectedFunctionError,
|
|
9
|
+
AbortedError
|
|
10
|
+
} = require('./errors.js')
|
|
11
|
+
const { mergeSparseArrays, getLowestNBits, nBitsOfOnes, toHex } = require('./helpers/functions.js')
|
|
12
|
+
const {
|
|
13
|
+
ChBitsAsc,
|
|
14
|
+
ChCodeAsc,
|
|
15
|
+
COMPRESSION_BINARY,
|
|
16
|
+
COMPRESSION_ASCII,
|
|
17
|
+
DICTIONARY_SIZE_SMALL,
|
|
18
|
+
DICTIONARY_SIZE_MEDIUM,
|
|
19
|
+
DICTIONARY_SIZE_LARGE,
|
|
20
|
+
PKDCL_OK,
|
|
21
|
+
PKDCL_STREAM_END,
|
|
22
|
+
LITERAL_STREAM_ABORTED,
|
|
23
|
+
LITERAL_END_STREAM,
|
|
24
|
+
LenBits,
|
|
25
|
+
LenBase,
|
|
26
|
+
ExLenBits,
|
|
27
|
+
DistBits,
|
|
28
|
+
LenCode,
|
|
29
|
+
DistCode
|
|
30
|
+
} = require('./constants.js')
|
|
31
|
+
const ExpandingBuffer = require('./helpers/ExpandingBuffer.js')
|
|
32
|
+
|
|
33
|
+
const readHeader = buffer => {
|
|
34
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
35
|
+
throw new ExpectedBufferError()
|
|
36
|
+
}
|
|
37
|
+
if (buffer.length < 4) {
|
|
38
|
+
throw new InvalidDataError()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const compressionType = buffer.readUInt8(0)
|
|
42
|
+
const dictionarySizeBits = buffer.readUInt8(1)
|
|
43
|
+
if (compressionType !== COMPRESSION_BINARY && compressionType !== COMPRESSION_ASCII) {
|
|
44
|
+
throw new InvalidCompressionTypeError()
|
|
45
|
+
}
|
|
46
|
+
if (!includes(dictionarySizeBits, [DICTIONARY_SIZE_SMALL, DICTIONARY_SIZE_MEDIUM, DICTIONARY_SIZE_LARGE])) {
|
|
47
|
+
throw new InvalidDictionarySizeError()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
compressionType,
|
|
52
|
+
dictionarySizeBits
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// PAT = populate ascii table
|
|
57
|
+
const createPATIterator = (limit, stepper) => n => {
|
|
58
|
+
return n >= limit ? false : [n, n + (1 << stepper)]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const populateAsciiTable = (value, index, bits, limit) => {
|
|
62
|
+
const iterator = createPATIterator(limit, value - bits)
|
|
63
|
+
const seed = ChCodeAsc[index] >> bits
|
|
64
|
+
const idxs = unfold(iterator, seed)
|
|
65
|
+
|
|
66
|
+
return reduce(
|
|
67
|
+
(acc, idx) => {
|
|
68
|
+
acc[idx] = index
|
|
69
|
+
return acc
|
|
70
|
+
},
|
|
71
|
+
[],
|
|
72
|
+
idxs
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const generateAsciiTables = () => {
|
|
77
|
+
const tables = {
|
|
78
|
+
asciiTable2C34: repeat(0, 0x100),
|
|
79
|
+
asciiTable2D34: repeat(0, 0x100),
|
|
80
|
+
asciiTable2E34: repeat(0, 0x80),
|
|
81
|
+
asciiTable2EB4: repeat(0, 0x100)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
tables.chBitsAsc = ChBitsAsc.map((value, index) => {
|
|
85
|
+
if (value <= 8) {
|
|
86
|
+
tables.asciiTable2C34 = mergeSparseArrays(populateAsciiTable(value, index, 0, 0x100), tables.asciiTable2C34)
|
|
87
|
+
return value - 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const acc = getLowestNBits(8, ChCodeAsc[index])
|
|
91
|
+
if (acc === 0) {
|
|
92
|
+
tables.asciiTable2EB4 = mergeSparseArrays(populateAsciiTable(value, index, 8, 0x100), tables.asciiTable2EB4)
|
|
93
|
+
return value - 8
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
tables.asciiTable2C34[acc] = 0xff
|
|
97
|
+
|
|
98
|
+
if (getLowestNBits(6, ChCodeAsc[index]) === 0) {
|
|
99
|
+
tables.asciiTable2E34 = mergeSparseArrays(populateAsciiTable(value, index, 6, 0x80), tables.asciiTable2E34)
|
|
100
|
+
return value - 6
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
tables.asciiTable2D34 = mergeSparseArrays(populateAsciiTable(value, index, 4, 0x100), tables.asciiTable2D34)
|
|
104
|
+
return value - 4
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
return tables
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const parseInitialData = (state, debug = false) => {
|
|
111
|
+
if (state.inputBuffer.size() < 4) {
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const { compressionType, dictionarySizeBits } = readHeader(state.inputBuffer.read())
|
|
116
|
+
|
|
117
|
+
state.compressionType = compressionType
|
|
118
|
+
state.dictionarySizeBits = dictionarySizeBits
|
|
119
|
+
state.bitBuffer = state.inputBuffer.read(2, 1)
|
|
120
|
+
state.inputBuffer.dropStart(3)
|
|
121
|
+
state.dictionarySizeMask = nBitsOfOnes(dictionarySizeBits)
|
|
122
|
+
|
|
123
|
+
if (compressionType === COMPRESSION_ASCII) {
|
|
124
|
+
const tables = generateAsciiTables()
|
|
125
|
+
Object.entries(tables).forEach(([key, value]) => {
|
|
126
|
+
state[key] = value
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (debug) {
|
|
131
|
+
console.log(`explode: compression type: ${state.compressionType === COMPRESSION_BINARY ? 'binary' : 'ascii'}`)
|
|
132
|
+
console.log(
|
|
133
|
+
`explode: compression level: ${
|
|
134
|
+
state.dictionarySizeBits === 4 ? 'small' : state.dictionarySizeBits === 5 ? 'medium' : 'large'
|
|
135
|
+
}`
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const wasteBits = (state, numberOfBits) => {
|
|
143
|
+
if (numberOfBits > state.extraBits && state.inputBuffer.isEmpty()) {
|
|
144
|
+
return PKDCL_STREAM_END
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (numberOfBits <= state.extraBits) {
|
|
148
|
+
state.bitBuffer = state.bitBuffer >> numberOfBits
|
|
149
|
+
state.extraBits = state.extraBits - numberOfBits
|
|
150
|
+
} else {
|
|
151
|
+
const nextByte = state.inputBuffer.read(0, 1)
|
|
152
|
+
state.inputBuffer.dropStart(1)
|
|
153
|
+
|
|
154
|
+
state.bitBuffer = ((state.bitBuffer >> state.extraBits) | (nextByte << 8)) >> (numberOfBits - state.extraBits)
|
|
155
|
+
state.extraBits = state.extraBits + 8 - numberOfBits
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return PKDCL_OK
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const decodeNextLiteral = state => {
|
|
162
|
+
const lastBit = state.bitBuffer & 1
|
|
163
|
+
|
|
164
|
+
if (wasteBits(state, 1) === PKDCL_STREAM_END) {
|
|
165
|
+
return LITERAL_STREAM_ABORTED
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (lastBit) {
|
|
169
|
+
let lengthCode = state.lengthCodes[getLowestNBits(8, state.bitBuffer)]
|
|
170
|
+
|
|
171
|
+
if (wasteBits(state, LenBits[lengthCode]) === PKDCL_STREAM_END) {
|
|
172
|
+
return LITERAL_STREAM_ABORTED
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const extraLenghtBits = ExLenBits[lengthCode]
|
|
176
|
+
if (extraLenghtBits !== 0) {
|
|
177
|
+
const extraLength = getLowestNBits(extraLenghtBits, state.bitBuffer)
|
|
178
|
+
|
|
179
|
+
if (wasteBits(state, extraLenghtBits) === PKDCL_STREAM_END && lengthCode + extraLength !== 0x10e) {
|
|
180
|
+
return LITERAL_STREAM_ABORTED
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
lengthCode = LenBase[lengthCode] + extraLength
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return lengthCode + 0x100
|
|
187
|
+
} else {
|
|
188
|
+
const lastByte = getLowestNBits(8, state.bitBuffer)
|
|
189
|
+
|
|
190
|
+
if (state.compressionType === COMPRESSION_BINARY) {
|
|
191
|
+
return wasteBits(state, 8) === PKDCL_STREAM_END ? LITERAL_STREAM_ABORTED : lastByte
|
|
192
|
+
} else {
|
|
193
|
+
let value
|
|
194
|
+
if (lastByte > 0) {
|
|
195
|
+
value = state.asciiTable2C34[lastByte]
|
|
196
|
+
|
|
197
|
+
if (value === 0xff) {
|
|
198
|
+
if (getLowestNBits(6, state.bitBuffer)) {
|
|
199
|
+
if (wasteBits(state, 4) === PKDCL_STREAM_END) {
|
|
200
|
+
return LITERAL_STREAM_ABORTED
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
value = state.asciiTable2D34[getLowestNBits(8, state.bitBuffer)]
|
|
204
|
+
} else {
|
|
205
|
+
if (wasteBits(state, 6) === PKDCL_STREAM_END) {
|
|
206
|
+
return LITERAL_STREAM_ABORTED
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
value = state.asciiTable2E34[getLowestNBits(7, state.bitBuffer)]
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
if (wasteBits(state, 8) === PKDCL_STREAM_END) {
|
|
214
|
+
return LITERAL_STREAM_ABORTED
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
value = state.asciiTable2EB4[getLowestNBits(8, state.bitBuffer)]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return wasteBits(state, state.chBitsAsc[value]) === PKDCL_STREAM_END ? LITERAL_STREAM_ABORTED : value
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const decodeDistance = (state, repeatLength) => {
|
|
226
|
+
const distPosCode = state.distPosCodes[getLowestNBits(8, state.bitBuffer)]
|
|
227
|
+
const distPosBits = DistBits[distPosCode]
|
|
228
|
+
if (wasteBits(state, distPosBits) === PKDCL_STREAM_END) {
|
|
229
|
+
return 0
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let distance
|
|
233
|
+
let bitsToWaste
|
|
234
|
+
|
|
235
|
+
if (repeatLength === 2) {
|
|
236
|
+
distance = (distPosCode << 2) | getLowestNBits(2, state.bitBuffer)
|
|
237
|
+
bitsToWaste = 2
|
|
238
|
+
} else {
|
|
239
|
+
distance = (distPosCode << state.dictionarySizeBits) | (state.bitBuffer & state.dictionarySizeMask)
|
|
240
|
+
bitsToWaste = state.dictionarySizeBits
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (wasteBits(state, bitsToWaste) === PKDCL_STREAM_END) {
|
|
244
|
+
return 0
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return distance + 1
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const processChunkData = (state, debug = false) => {
|
|
251
|
+
if (state.inputBuffer.isEmpty()) {
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!has('compressionType', state)) {
|
|
256
|
+
const parsedHeader = parseInitialData(state, debug)
|
|
257
|
+
if (!parsedHeader || state.inputBuffer.isEmpty()) {
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
state.needMoreInput = false
|
|
263
|
+
|
|
264
|
+
state.backup()
|
|
265
|
+
let nextLiteral = decodeNextLiteral(state)
|
|
266
|
+
|
|
267
|
+
while (nextLiteral !== LITERAL_END_STREAM && nextLiteral !== LITERAL_STREAM_ABORTED) {
|
|
268
|
+
let addition
|
|
269
|
+
if (nextLiteral >= 0x100) {
|
|
270
|
+
const repeatLength = nextLiteral - 0xfe
|
|
271
|
+
const minusDistance = decodeDistance(state, repeatLength)
|
|
272
|
+
if (minusDistance === 0) {
|
|
273
|
+
state.needMoreInput = true
|
|
274
|
+
break
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const availableData = state.outputBuffer.read(state.outputBuffer.size() - minusDistance, repeatLength)
|
|
278
|
+
|
|
279
|
+
if (repeatLength > minusDistance) {
|
|
280
|
+
const multipliedData = repeat(availableData, Math.ceil(repeatLength / availableData.length))
|
|
281
|
+
addition = Buffer.concat(multipliedData).slice(0, repeatLength)
|
|
282
|
+
} else {
|
|
283
|
+
addition = availableData
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
addition = Buffer.from([nextLiteral])
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
state.outputBuffer.append(addition)
|
|
290
|
+
|
|
291
|
+
state.backup()
|
|
292
|
+
nextLiteral = decodeNextLiteral(state)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (nextLiteral === LITERAL_STREAM_ABORTED) {
|
|
296
|
+
state.needMoreInput = true
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (state.needMoreInput) {
|
|
300
|
+
state.restore()
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const generateDecodeTables = (startIndexes, lengthBits) => {
|
|
305
|
+
return lengthBits.reduce((acc, lengthBit, i) => {
|
|
306
|
+
for (let index = startIndexes[i]; index < 0x100; index += 1 << lengthBit) {
|
|
307
|
+
acc[index] = i
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return acc
|
|
311
|
+
}, repeat(0, 0x100))
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const explode = (config = {}) => {
|
|
315
|
+
const { debug = false, inputBufferSize = 0x0, outputBufferSize = 0x0 } = config
|
|
316
|
+
|
|
317
|
+
const handler = function (chunk, encoding, callback) {
|
|
318
|
+
if (!isFunction(callback)) {
|
|
319
|
+
// can't call callback to pass in data or errors, so we throw up
|
|
320
|
+
throw new ExpectedFunctionError()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const state = handler._state
|
|
324
|
+
state.needMoreInput = true
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
state.inputBuffer.append(chunk)
|
|
328
|
+
if (state.isFirstChunk) {
|
|
329
|
+
state.isFirstChunk = false
|
|
330
|
+
this._flush = state.onInputFinished
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (debug) {
|
|
334
|
+
console.log(`explode: reading ${toHex(chunk.length)} bytes from chunk #${state.stats.chunkCounter++}`)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
processChunkData(state, debug)
|
|
338
|
+
|
|
339
|
+
const blockSize = 0x1000
|
|
340
|
+
if (state.outputBuffer.size() > blockSize) {
|
|
341
|
+
const numberOfBytes = (Math.floor(state.outputBuffer.size() / blockSize) - 1) * blockSize
|
|
342
|
+
const output = Buffer.from(state.outputBuffer.read(0, numberOfBytes))
|
|
343
|
+
state.outputBuffer.flushStart(numberOfBytes)
|
|
344
|
+
|
|
345
|
+
callback(null, output)
|
|
346
|
+
} else {
|
|
347
|
+
callback(null, Buffer.from([]))
|
|
348
|
+
}
|
|
349
|
+
} catch (e) {
|
|
350
|
+
callback(e)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
handler._state = {
|
|
355
|
+
_backup: {
|
|
356
|
+
extraBits: null,
|
|
357
|
+
bitBuffer: null
|
|
358
|
+
},
|
|
359
|
+
needMoreInput: true,
|
|
360
|
+
isFirstChunk: true,
|
|
361
|
+
extraBits: 0,
|
|
362
|
+
chBitsAsc: repeat(0, 0x100), // DecodeLit and GenAscTabs uses this
|
|
363
|
+
lengthCodes: generateDecodeTables(LenCode, LenBits),
|
|
364
|
+
distPosCodes: generateDecodeTables(DistCode, DistBits),
|
|
365
|
+
inputBuffer: new ExpandingBuffer(inputBufferSize),
|
|
366
|
+
outputBuffer: new ExpandingBuffer(outputBufferSize),
|
|
367
|
+
onInputFinished: callback => {
|
|
368
|
+
const state = handler._state
|
|
369
|
+
|
|
370
|
+
if (debug) {
|
|
371
|
+
console.log('---------------')
|
|
372
|
+
console.log('explode: total number of chunks read:', state.stats.chunkCounter)
|
|
373
|
+
console.log('explode: inputBuffer heap size', toHex(state.inputBuffer.heapSize()))
|
|
374
|
+
console.log('explode: outputBuffer heap size', toHex(state.outputBuffer.heapSize()))
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (state.needMoreInput) {
|
|
378
|
+
callback(new AbortedError())
|
|
379
|
+
} else {
|
|
380
|
+
callback(null, state.outputBuffer.read())
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
backup: () => {
|
|
384
|
+
const state = handler._state
|
|
385
|
+
state._backup.extraBits = state.extraBits
|
|
386
|
+
state._backup.bitBuffer = state.bitBuffer
|
|
387
|
+
state.inputBuffer._saveIndices()
|
|
388
|
+
},
|
|
389
|
+
restore: () => {
|
|
390
|
+
const state = handler._state
|
|
391
|
+
state.extraBits = state._backup.extraBits
|
|
392
|
+
state.bitBuffer = state._backup.bitBuffer
|
|
393
|
+
state.inputBuffer._restoreIndices()
|
|
394
|
+
},
|
|
395
|
+
stats: {
|
|
396
|
+
chunkCounter: 0
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return handler
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
module.exports = {
|
|
404
|
+
readHeader,
|
|
405
|
+
explode,
|
|
406
|
+
createPATIterator,
|
|
407
|
+
populateAsciiTable,
|
|
408
|
+
generateAsciiTables,
|
|
409
|
+
processChunkData,
|
|
410
|
+
wasteBits,
|
|
411
|
+
decodeNextLiteral,
|
|
412
|
+
decodeDistance,
|
|
413
|
+
generateDecodeTables
|
|
414
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
const { clamp } = require('ramda')
|
|
2
|
+
const { ExpectedBufferError } = require('../errors')
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
class ExpandingBuffer {
|
|
4
5
|
constructor(numberOfBytes = 0) {
|
|
5
6
|
this._heap = Buffer.allocUnsafe(numberOfBytes)
|
|
6
7
|
this._startIndex = 0
|
|
@@ -29,6 +30,10 @@ export default class QuasiImmutableBuffer {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
append(buffer) {
|
|
33
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
34
|
+
throw new ExpectedBufferError()
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
if (this._endIndex + buffer.length < this.heapSize()) {
|
|
33
38
|
buffer.copy(this._heap, this._endIndex)
|
|
34
39
|
this._endIndex += buffer.length
|
|
@@ -39,6 +44,7 @@ export default class QuasiImmutableBuffer {
|
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
46
|
|
|
47
|
+
// watch out! the buffer returned by Buffer.slice() will point to the same memory!
|
|
42
48
|
read(offset, limit) {
|
|
43
49
|
if (offset < 0 || limit < 1) {
|
|
44
50
|
return Buffer.from([])
|
|
@@ -54,6 +60,8 @@ export default class QuasiImmutableBuffer {
|
|
|
54
60
|
return this._getActualData(offset)
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
// hard delete
|
|
64
|
+
// removes data from the buffer by copying bytes to lower indices
|
|
57
65
|
flushStart(numberOfBytes) {
|
|
58
66
|
numberOfBytes = clamp(0, this.heapSize(), numberOfBytes)
|
|
59
67
|
if (numberOfBytes > 0) {
|
|
@@ -72,6 +80,8 @@ export default class QuasiImmutableBuffer {
|
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
|
|
83
|
+
// soft delete
|
|
84
|
+
// removes data from the buffer by moving the startIndex forward
|
|
75
85
|
dropStart(numberOfBytes) {
|
|
76
86
|
if (numberOfBytes > 0) {
|
|
77
87
|
this._startIndex += numberOfBytes
|
|
@@ -109,3 +119,5 @@ export default class QuasiImmutableBuffer {
|
|
|
109
119
|
this._endIndex = this._backup._endIndex
|
|
110
120
|
}
|
|
111
121
|
}
|
|
122
|
+
|
|
123
|
+
module.exports = ExpandingBuffer
|