Skip to content

Commit 37e4751

Browse files
authored
Fix lzxpress decompression on windows. (#5)
There seems to be some problem decompression some files. This change falls back to manual decompression on if the API is not available (on older windows or non-windows system). On windows we call the windows API to decompress the prefetch file in a more reliable way. Fixes: #4
1 parent 0469fa2 commit 37e4751

File tree

7 files changed

+120
-14
lines changed

7 files changed

+120
-14
lines changed

cmd/prefetch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func main() {
5151
binparsergen.FatalIfError(err, fmt.Sprintf("Open file: %v", err))
5252
}
5353

54-
decompressed, err := prefetch.LZXpressHuffmanDecompress(
54+
decompressed, err := prefetch.LZXpressHuffmanDecompressWithFallback(
5555
data[:n], int(header.UncompressedSize()))
5656
binparsergen.FatalIfError(err, fmt.Sprintf("Open file: %v", err))
5757

go.mod

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module www.velocidex.com/golang/go-prefetch
2+
3+
go 1.14
4+
5+
require (
6+
github.com/davecgh/go-spew v1.1.1
7+
github.com/jawher/mow.cli v1.1.0
8+
github.com/sebdah/goldie v1.0.0
9+
github.com/stretchr/testify v1.3.0
10+
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666
11+
gopkg.in/yaml.v2 v2.2.2 // indirect
12+
www.velocidex.com/golang/binparsergen v0.1.0
13+
)

go.sum

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/jawher/mow.cli v1.1.0 h1:NdtHXRc0CwZQ507wMvQ/IS+Q3W3x2fycn973/b8Zuk8=
5+
github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg=
6+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8+
github.com/sebdah/goldie v1.0.0 h1:9GNhIat69MSlz/ndaBg48vl9dF5fI+NBB6kfOxgfkMc=
9+
github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
10+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
11+
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
12+
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
13+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
14+
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 h1:gVCS+QOncANNPlmlO1AhlU3oxs4V9z+gTtPwIk3p2N8=
15+
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
16+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
17+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
18+
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
19+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
20+
www.velocidex.com/golang/binparsergen v0.1.0 h1:oNsMHGnlb4jrGwxKxqqmsics6FgYin3HR5UNtLXc8S0=
21+
www.velocidex.com/golang/binparsergen v0.1.0/go.mod h1:UC43Ecj0mjsidlClTYZ3H4dXdyv7CVI0HsYi4yY3qtc=

lzxpress.go

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ Returns the index in treeNodes of the next node to be processed.
9797
*/
9898
func PrefixCodeTreeAddLeaf(
9999
treeNodes []PREFIX_CODE_NODE,
100-
leafIndex int,
100+
leafIndex uint32,
101101
mask uint32,
102-
bits uint32) int {
102+
bits uint32) uint32 {
103103

104104
node := &treeNodes[0]
105105
i := leafIndex + 1
@@ -126,13 +126,17 @@ func PrefixCodeTreeRebuild(input []byte) *PREFIX_CODE_NODE {
126126
symbolInfo := make([]PREFIX_CODE_SYMBOL, 512)
127127

128128
for i := 0; i < 256; i++ {
129+
value := input[i]
130+
129131
symbolInfo[2*i].id = uint32(2 * i)
130132
symbolInfo[2*i].symbol = uint32(2 * i)
131-
symbolInfo[2*i].length = uint32(input[i] & 15)
133+
symbolInfo[2*i].length = uint32(value & 0xf)
134+
135+
value >>= 4
132136

133137
symbolInfo[2*i+1].id = uint32(2*i + 1)
134138
symbolInfo[2*i+1].symbol = uint32(2*i + 1)
135-
symbolInfo[2*i+1].length = uint32(input[i] >> 4)
139+
symbolInfo[2*i+1].length = uint32(value & 0xf)
136140
}
137141

138142
sort.SliceStable(symbolInfo, func(i, j int) bool {
@@ -150,7 +154,8 @@ func PrefixCodeTreeRebuild(input []byte) *PREFIX_CODE_NODE {
150154
})
151155

152156
i := 0
153-
for ; i < 512 && symbolInfo[i].length == 0; i++ {
157+
for i < 512 && symbolInfo[i].length == 0 {
158+
i++
154159
}
155160

156161
mask := uint32(0)
@@ -159,7 +164,7 @@ func PrefixCodeTreeRebuild(input []byte) *PREFIX_CODE_NODE {
159164
root := &treeNodes[0]
160165
root.leaf = false
161166

162-
j := 1
167+
j := uint32(1)
163168
for ; i < 512; i++ {
164169
treeNodes[j].id = uint32(j)
165170
treeNodes[j].symbol = symbolInfo[i].symbol
@@ -191,7 +196,6 @@ func PrefixCodeTreeDecodeSymbol(bstr *BitStream, root *PREFIX_CODE_NODE) (
191196
}
192197

193198
return node.symbol, nil
194-
195199
}
196200

197201
func LZXpressHuffmanDecompressChunk(
@@ -201,6 +205,12 @@ func LZXpressHuffmanDecompressChunk(
201205
output []byte, // The output buffer.
202206
chunk_size int, // The required size of uncompressed buffer
203207
) (int, int, error) {
208+
209+
// There must be at least this many bytes available to read.
210+
if in_idx+256 > len(input) {
211+
return 0, 0, io.EOF
212+
}
213+
204214
root := PrefixCodeTreeRebuild(input[in_idx:])
205215
bstr := NewBitStream(input, in_idx+256)
206216

@@ -220,10 +230,15 @@ func LZXpressHuffmanDecompressChunk(
220230

221231
} else {
222232
symbol -= 256
223-
length := symbol & 15
233+
length := uint32(symbol & 15)
224234
symbol >>= 4
225235

226-
offset := (1 << symbol) + bstr.Lookup(symbol)
236+
offset := int32(0)
237+
if symbol != 0 {
238+
offset = int32(bstr.Lookup(symbol))
239+
}
240+
offset |= 1 << symbol
241+
offset = -offset
227242

228243
if length == 15 {
229244
length = uint32(bstr.source[bstr.index]) + 15
@@ -236,16 +251,19 @@ func LZXpressHuffmanDecompressChunk(
236251
}
237252
}
238253

239-
bstr.Skip(symbol)
254+
err := bstr.Skip(symbol)
255+
if err != nil {
256+
return int(bstr.index), i, err
257+
}
240258

241259
length = length + 3
242260
for {
243-
if i-int(offset) < 0 {
261+
if i+int(offset) < 0 {
244262
return int(bstr.index),
245263
i, errors.New("Decompression error")
246264
}
247265

248-
output[i] = output[i-int(offset)]
266+
output[i] = output[i+int(offset)]
249267
i++
250268
length -= 1
251269
if length == 0 {

lzxpress_default.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// +build !windows
2+
3+
package prefetch
4+
5+
// Non windows systems fall back to build in decompression.
6+
func LZXpressHuffmanDecompressWithFallback(input []byte, output_size int) ([]byte, error) {
7+
return LZXpressHuffmanDecompress(input, output_size)
8+
}

lzxpress_windows.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// +build windows
2+
3+
package prefetch
4+
5+
import (
6+
"unsafe"
7+
8+
"golang.org/x/sys/windows"
9+
)
10+
11+
var (
12+
ntoskrnl = windows.NewLazySystemDLL("ntdll.dll")
13+
RtlDecompressBufferEx = ntoskrnl.NewProc("RtlDecompressBufferEx")
14+
15+
COMPRESSION_FORMAT_XPRESS_HUFF = uint16(0x0004)
16+
)
17+
18+
func LZXpressHuffmanDecompressWithFallback(input []byte, output_size int) ([]byte, error) {
19+
20+
// For older windows, we fall back to the build in decompression.
21+
err := RtlDecompressBufferEx.Find()
22+
if err != nil {
23+
return LZXpressHuffmanDecompress(input, output_size)
24+
}
25+
26+
result := make([]byte, output_size)
27+
final_size := uint32(output_size)
28+
29+
workspace := make([]byte, output_size*2)
30+
31+
ret, _, _ := RtlDecompressBufferEx.Call(
32+
uintptr(COMPRESSION_FORMAT_XPRESS_HUFF),
33+
uintptr(unsafe.Pointer(&result[0])),
34+
uintptr(output_size),
35+
uintptr(unsafe.Pointer(&input[0])),
36+
uintptr(len(input)),
37+
uintptr(unsafe.Pointer(&final_size)),
38+
uintptr(unsafe.Pointer(&workspace[0])))
39+
if ret != 0 {
40+
// Fallback to the built in implementation on error.
41+
return LZXpressHuffmanDecompress(input, output_size)
42+
}
43+
44+
// Return the decompressed buffer from the API.
45+
return result[:final_size], nil
46+
}

prefetch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func LoadPrefetch(reader io.ReaderAt) (*PrefetchInfo, error) {
2323
return nil, err
2424
}
2525

26-
decompressed, err := LZXpressHuffmanDecompress(
26+
decompressed, err := LZXpressHuffmanDecompressWithFallback(
2727
data[:n], int(header.UncompressedSize()))
2828
if err != nil {
2929
return nil, err

0 commit comments

Comments
 (0)