ruby_smb 2.0.3 → 2.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -2
- data/.github/workflows/verify.yml +56 -0
- data/README.md +0 -1
- data/examples/delete_file.rb +0 -0
- data/examples/net_share_enum_all.rb +0 -0
- data/examples/pipes.rb +0 -0
- data/examples/rename_file.rb +0 -0
- data/lib/ruby_smb.rb +3 -2
- data/lib/ruby_smb/client.rb +12 -8
- data/lib/ruby_smb/client/authentication.rb +5 -10
- data/lib/ruby_smb/client/echo.rb +2 -4
- data/lib/ruby_smb/client/negotiation.rb +4 -6
- data/lib/ruby_smb/client/tree_connect.rb +2 -4
- data/lib/ruby_smb/client/utils.rb +16 -10
- data/lib/ruby_smb/client/winreg.rb +1 -1
- data/lib/ruby_smb/compression.rb +7 -0
- data/lib/ruby_smb/compression/lznt1.rb +164 -0
- data/lib/ruby_smb/dcerpc.rb +3 -1
- data/lib/ruby_smb/dcerpc/ndr.rb +97 -0
- data/lib/ruby_smb/dcerpc/netlogon.rb +101 -0
- data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request.rb +37 -0
- data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response.rb +26 -0
- data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request.rb +37 -0
- data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response.rb +23 -0
- data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request.rb +32 -0
- data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response.rb +24 -0
- data/lib/ruby_smb/dcerpc/request.rb +6 -0
- data/lib/ruby_smb/dcerpc/winreg.rb +2 -2
- data/lib/ruby_smb/dispatcher/socket.rb +1 -1
- data/lib/ruby_smb/error.rb +21 -5
- data/lib/ruby_smb/generic_packet.rb +11 -1
- data/lib/ruby_smb/smb1/file.rb +8 -15
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +0 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +0 -1
- data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +1 -2
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +1 -13
- data/lib/ruby_smb/smb1/pipe.rb +8 -8
- data/lib/ruby_smb/smb1/tree.rb +13 -9
- data/lib/ruby_smb/smb2/file.rb +8 -16
- data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +4 -0
- data/lib/ruby_smb/smb2/pipe.rb +8 -8
- data/lib/ruby_smb/smb2/tree.rb +12 -8
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/client_spec.rb +17 -12
- data/spec/lib/ruby_smb/compression/lznt1_spec.rb +32 -0
- data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request_spec.rb +69 -0
- data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response_spec.rb +53 -0
- data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request_spec.rb +69 -0
- data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response_spec.rb +37 -0
- data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request_spec.rb +45 -0
- data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response_spec.rb +37 -0
- data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +12 -0
- data/spec/lib/ruby_smb/error_spec.rb +34 -5
- data/spec/lib/ruby_smb/generic_packet_spec.rb +7 -0
- data/spec/lib/ruby_smb/smb1/file_spec.rb +2 -4
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +0 -1
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +0 -1
- data/spec/lib/ruby_smb/smb1/packet/trans2/open2_response_spec.rb +0 -5
- data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +0 -6
- data/spec/lib/ruby_smb/smb1/pipe_spec.rb +23 -5
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +22 -0
- data/spec/lib/ruby_smb/smb2/file_spec.rb +1 -3
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +2 -5
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +23 -0
- data/spec/spec_helper.rb +1 -1
- metadata +42 -19
- metadata.gz.sig +0 -0
- data/.travis.yml +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3cfb7914736c49c84c366382e133ea0c2e9a5ecb6f0a3badceb498652bbaa76
|
4
|
+
data.tar.gz: 6f47a0ad156545a259d0496f9e1c59d50b6327889600b6acda0e93ec5c835963
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90c181090093eeb71d4ef508a2b1e7b75ccb31b7e40cdee973397f7554af1085e8b39a518f43e9e4e3eba5fd12d0677d1f059ed614eab8dfb5b0db74a956236a
|
7
|
+
data.tar.gz: 443e97e78383d44deb155c0d6e3ca8bc3923c49b2561fa41670e09d7916cdfaa7223143a29b24697699e7470cdcaf6f71a2e0e4167d8c19a7b80b2fc56b2a555
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
|
2
|
-
��� Hv�mFU�3�)���*q��qPμg�B�2�Д�0�ߤ����D�5#��f���b���5����05�f| ��0��7�o
|
1
|
+
�Ss�.�倮�����;�ͦ��c7-ȡ��.�84����R�Q�.]��^�_��w� =r��ܖhu9r(�9��0�D@�˫"c��z�N{-��F~U�3����@/��nD~,O���B��^tƯ�F>�Ż��4��tתp@ҫw�,B������j9��
|
3
2
|
��J�t��&kk��94��*��GTL9p�C
|
@@ -0,0 +1,56 @@
|
|
1
|
+
name: Verify
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- '*'
|
7
|
+
pull_request:
|
8
|
+
branches:
|
9
|
+
- '*'
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
test:
|
13
|
+
runs-on: ubuntu-16.04
|
14
|
+
timeout-minutes: 40
|
15
|
+
|
16
|
+
strategy:
|
17
|
+
fail-fast: true
|
18
|
+
matrix:
|
19
|
+
ruby:
|
20
|
+
- 2.5
|
21
|
+
- 2.6
|
22
|
+
- 2.7
|
23
|
+
test_cmd:
|
24
|
+
- bundle exec rspec
|
25
|
+
|
26
|
+
env:
|
27
|
+
RAILS_ENV: test
|
28
|
+
|
29
|
+
name: Ruby ${{ matrix.ruby }} - ${{ matrix.test_cmd }}
|
30
|
+
steps:
|
31
|
+
- name: Checkout code
|
32
|
+
uses: actions/checkout@v2
|
33
|
+
|
34
|
+
- uses: actions/setup-ruby@v1
|
35
|
+
with:
|
36
|
+
ruby-version: ${{ matrix.ruby }}
|
37
|
+
|
38
|
+
- name: Setup bundler
|
39
|
+
run: |
|
40
|
+
gem install bundler
|
41
|
+
- uses: actions/cache@v2
|
42
|
+
with:
|
43
|
+
path: vendor/bundle
|
44
|
+
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
|
45
|
+
restore-keys: |
|
46
|
+
${{ runner.os }}-gems-
|
47
|
+
- name: Bundle install
|
48
|
+
run: |
|
49
|
+
bundle config path vendor/bundle
|
50
|
+
bundle install --jobs 4 --retry 3
|
51
|
+
- name: ${{ matrix.test_cmd }}
|
52
|
+
run: |
|
53
|
+
echo "${CMD}"
|
54
|
+
bash -c "${CMD}"
|
55
|
+
env:
|
56
|
+
CMD: ${{ matrix.test_cmd }}
|
data/README.md
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# RubySMB
|
2
2
|
|
3
|
-
[![Build Status](https://travis-ci.org/rapid7/ruby_smb.svg?branch=master)](https://travis-ci.org/rapid7/ruby_smb)
|
4
3
|
[![Code Climate](https://codeclimate.com/github/rapid7/ruby_smb.png)](https://codeclimate.com/github/rapid7/ruby_smb)
|
5
4
|
[![Coverage Status](https://coveralls.io/repos/github/rapid7/ruby_smb/badge.svg?branch=master)](https://coveralls.io/github/rapid7/ruby_smb?branch=master)
|
6
5
|
|
data/examples/delete_file.rb
CHANGED
File without changes
|
File without changes
|
data/examples/pipes.rb
CHANGED
File without changes
|
data/examples/rename_file.rb
CHANGED
File without changes
|
data/lib/ruby_smb.rb
CHANGED
@@ -8,8 +8,8 @@ require 'windows_error'
|
|
8
8
|
require 'windows_error/nt_status'
|
9
9
|
# A packet parsing and manipulation library for the SMB1 and SMB2 protocols
|
10
10
|
#
|
11
|
-
# [[MS-SMB] Server
|
12
|
-
# [[MS-SMB2] Server
|
11
|
+
# [[MS-SMB] Server Message Block (SMB) Protocol Version 1](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
|
12
|
+
# [[MS-SMB2] Server Message Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
|
13
13
|
module RubySMB
|
14
14
|
require 'ruby_smb/error'
|
15
15
|
require 'ruby_smb/dispositions'
|
@@ -26,4 +26,5 @@ module RubySMB
|
|
26
26
|
require 'ruby_smb/smb1'
|
27
27
|
require 'ruby_smb/client'
|
28
28
|
require 'ruby_smb/crypto'
|
29
|
+
require 'ruby_smb/compression'
|
29
30
|
end
|
data/lib/ruby_smb/client.rb
CHANGED
@@ -296,7 +296,7 @@ module RubySMB
|
|
296
296
|
@smb3 = smb3
|
297
297
|
@username = username.encode('utf-8') || ''.encode('utf-8')
|
298
298
|
@max_buffer_size = MAX_BUFFER_SIZE
|
299
|
-
# These sizes will be
|
299
|
+
# These sizes will be modified during negotiation
|
300
300
|
@server_max_buffer_size = SERVER_MAX_BUFFER_SIZE
|
301
301
|
@server_max_read_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
|
302
302
|
@server_max_write_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
|
@@ -411,8 +411,7 @@ module RubySMB
|
|
411
411
|
raise RubySMB::Error::InvalidPacket.new(
|
412
412
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
413
413
|
expected_cmd: RubySMB::SMB2::Packet::LogoffResponse::COMMAND,
|
414
|
-
|
415
|
-
received_cmd: response.smb2_header.command
|
414
|
+
packet: response
|
416
415
|
)
|
417
416
|
end
|
418
417
|
else
|
@@ -423,8 +422,7 @@ module RubySMB
|
|
423
422
|
raise RubySMB::Error::InvalidPacket.new(
|
424
423
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
425
424
|
expected_cmd: RubySMB::SMB1::Packet::LogoffResponse::COMMAND,
|
426
|
-
|
427
|
-
received_cmd: response.smb_header.command
|
425
|
+
packet: response
|
428
426
|
)
|
429
427
|
end
|
430
428
|
end
|
@@ -433,7 +431,7 @@ module RubySMB
|
|
433
431
|
end
|
434
432
|
|
435
433
|
# Sends a packet and receives the raw response through the Dispatcher.
|
436
|
-
# It will also sign the packet if
|
434
|
+
# It will also sign the packet if necessary.
|
437
435
|
#
|
438
436
|
# @param packet [RubySMB::GenericPacket] the request to be sent
|
439
437
|
# @param encrypt [Boolean] true if encryption has to be enabled for this transaction
|
@@ -473,11 +471,17 @@ module RubySMB
|
|
473
471
|
break unless is_status_pending?(smb2_header)
|
474
472
|
sleep 1
|
475
473
|
raw_response = recv_packet(encrypt: encrypt_data)
|
474
|
+
rescue IOError
|
475
|
+
# We're expecting an SMB2 packet, but the server sent an SMB1 packet
|
476
|
+
# instead. This behavior has been observed with older versions of Samba
|
477
|
+
# when something goes wrong on the server side. So, we just ignore it
|
478
|
+
# and expect the caller to handle this wrong response packet.
|
479
|
+
break
|
476
480
|
end unless version == 'SMB1'
|
477
481
|
|
478
482
|
self.sequence_counter += 1 if signing_required && !session_key.empty?
|
479
483
|
# update the SMB2 message ID according to the received Credit Charged
|
480
|
-
self.smb2_message_id += smb2_header.credit_charge - 1 if smb2_header && self.
|
484
|
+
self.smb2_message_id += smb2_header.credit_charge - 1 if smb2_header && self.server_supports_multi_credit
|
481
485
|
raw_response
|
482
486
|
end
|
483
487
|
|
@@ -579,7 +583,7 @@ module RubySMB
|
|
579
583
|
# @param [String] host
|
580
584
|
def net_share_enum_all(host)
|
581
585
|
tree = tree_connect("\\\\#{host}\\IPC$")
|
582
|
-
named_pipe = tree.
|
586
|
+
named_pipe = tree.open_pipe(filename: "srvsvc", write: true, read: true)
|
583
587
|
named_pipe.net_share_enum_all(host)
|
584
588
|
end
|
585
589
|
|
@@ -60,8 +60,7 @@ module RubySMB
|
|
60
60
|
raise RubySMB::Error::InvalidPacket.new(
|
61
61
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
62
62
|
expected_cmd: RubySMB::SMB1::Packet::SessionSetupLegacyResponse::COMMAND,
|
63
|
-
|
64
|
-
received_cmd: packet.smb_header.command
|
63
|
+
packet: packet
|
65
64
|
)
|
66
65
|
end
|
67
66
|
packet
|
@@ -154,8 +153,7 @@ module RubySMB
|
|
154
153
|
raise RubySMB::Error::InvalidPacket.new(
|
155
154
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
156
155
|
expected_cmd: RubySMB::SMB1::Packet::SessionSetupResponse::COMMAND,
|
157
|
-
|
158
|
-
received_cmd: packet.smb_header.command
|
156
|
+
packet: packet
|
159
157
|
)
|
160
158
|
end
|
161
159
|
packet
|
@@ -168,8 +166,7 @@ module RubySMB
|
|
168
166
|
raise RubySMB::Error::InvalidPacket.new(
|
169
167
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
170
168
|
expected_cmd: RubySMB::SMB1::Packet::SessionSetupResponse::COMMAND,
|
171
|
-
|
172
|
-
received_cmd: packet.smb_header.command
|
169
|
+
packet: packet
|
173
170
|
)
|
174
171
|
end
|
175
172
|
|
@@ -236,8 +233,7 @@ module RubySMB
|
|
236
233
|
raise RubySMB::Error::InvalidPacket.new(
|
237
234
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
238
235
|
expected_cmd: RubySMB::SMB2::Packet::SessionSetupResponse::COMMAND,
|
239
|
-
|
240
|
-
received_cmd: packet.smb2_header.command
|
236
|
+
packet: packet
|
241
237
|
)
|
242
238
|
end
|
243
239
|
|
@@ -251,8 +247,7 @@ module RubySMB
|
|
251
247
|
raise RubySMB::Error::InvalidPacket.new(
|
252
248
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
253
249
|
expected_cmd: RubySMB::SMB2::Packet::SessionSetupResponse::COMMAND,
|
254
|
-
|
255
|
-
received_cmd: packet.smb2_header.command
|
250
|
+
packet: packet
|
256
251
|
)
|
257
252
|
end
|
258
253
|
|
data/lib/ruby_smb/client/echo.rb
CHANGED
@@ -21,8 +21,7 @@ module RubySMB
|
|
21
21
|
raise RubySMB::Error::InvalidPacket.new(
|
22
22
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
23
23
|
expected_cmd: RubySMB::SMB1::Packet::EchoResponse::COMMAND,
|
24
|
-
|
25
|
-
received_cmd: response.smb_header.command
|
24
|
+
packet: response
|
26
25
|
)
|
27
26
|
end
|
28
27
|
response
|
@@ -40,8 +39,7 @@ module RubySMB
|
|
40
39
|
raise RubySMB::Error::InvalidPacket.new(
|
41
40
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
42
41
|
expected_cmd: RubySMB::SMB2::Packet::EchoResponse::COMMAND,
|
43
|
-
|
44
|
-
received_cmd: response.smb2_header.command
|
42
|
+
packet: response
|
45
43
|
)
|
46
44
|
end
|
47
45
|
response
|
@@ -83,16 +83,14 @@ module RubySMB
|
|
83
83
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
84
84
|
expected_cmd: RubySMB::SMB1::Packet::NegotiateResponseExtended::COMMAND,
|
85
85
|
expected_custom: "extended_security=1",
|
86
|
-
|
87
|
-
received_cmd: packet.smb_header.command,
|
86
|
+
packet: packet,
|
88
87
|
received_custom: "extended_security=#{extended_security}"
|
89
88
|
)
|
90
89
|
elsif packet.packet_smb_version == 'SMB2'
|
91
90
|
raise RubySMB::Error::InvalidPacket.new(
|
92
91
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
93
92
|
expected_cmd: RubySMB::SMB2::Packet::NegotiateResponse::COMMAND,
|
94
|
-
|
95
|
-
received_cmd: packet.smb2_header.command
|
93
|
+
packet: packet
|
96
94
|
)
|
97
95
|
else
|
98
96
|
raise RubySMB::Error::InvalidPacket, 'Unknown SMB protocol version'
|
@@ -277,8 +275,8 @@ module RubySMB
|
|
277
275
|
context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
278
276
|
)
|
279
277
|
# Adding all possible compression algorithm even if we don't support
|
280
|
-
# them yet. This will force the server to disclose the
|
281
|
-
# algorithms in the
|
278
|
+
# them yet. This will force the server to disclose the supported
|
279
|
+
# algorithms in the response.
|
282
280
|
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
|
283
281
|
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
|
284
282
|
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
|
@@ -33,8 +33,7 @@ module RubySMB
|
|
33
33
|
raise RubySMB::Error::InvalidPacket.new(
|
34
34
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
35
35
|
expected_cmd: RubySMB::SMB1::Packet::TreeConnectResponse::COMMAND,
|
36
|
-
|
37
|
-
received_cmd: response.smb_header.command
|
36
|
+
packet: response
|
38
37
|
)
|
39
38
|
end
|
40
39
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
@@ -73,8 +72,7 @@ module RubySMB
|
|
73
72
|
raise RubySMB::Error::InvalidPacket.new(
|
74
73
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
75
74
|
expected_cmd: RubySMB::SMB2::Packet::TreeConnectResponse::COMMAND,
|
76
|
-
|
77
|
-
received_cmd: response.smb2_header.command
|
75
|
+
packet: response
|
78
76
|
)
|
79
77
|
end
|
80
78
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
@@ -24,19 +24,25 @@ module RubySMB
|
|
24
24
|
last_tree.id
|
25
25
|
end
|
26
26
|
|
27
|
-
def open(path, disposition=RubySMB::Dispositions::FILE_OPEN, write: false, read: true)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
27
|
+
def open(path, disposition=RubySMB::Dispositions::FILE_OPEN, write: false, read: true, pipe: false)
|
28
|
+
if pipe
|
29
|
+
file = last_tree.open_pipe(filename: path, write: write, read: read, disposition: disposition)
|
30
|
+
else
|
31
|
+
file = last_tree.open_file(filename: path, write: write, read: read, disposition: disposition)
|
32
|
+
end
|
33
|
+
@last_file_id = if file.respond_to?(:guid)
|
34
|
+
# SMB2 uses guid
|
35
|
+
file.guid.to_binary_s
|
36
|
+
elsif file.respond_to?(:fid)
|
37
|
+
# SMB1 uses fid
|
38
|
+
file.fid.to_binary_s
|
39
|
+
end
|
40
|
+
@open_files[@last_file_id] = file
|
41
|
+
@last_file_id
|
36
42
|
end
|
37
43
|
|
38
44
|
def create_pipe(path, disposition=RubySMB::Dispositions::FILE_OPEN_IF)
|
39
|
-
open(path
|
45
|
+
open(path, disposition, write: true, read: true, pipe: true)
|
40
46
|
end
|
41
47
|
|
42
48
|
#Writes data to an open file handle
|
@@ -6,7 +6,7 @@ module RubySMB
|
|
6
6
|
share = "\\\\#{host}\\IPC$"
|
7
7
|
tree = @tree_connects.find {|tree| tree.share == share}
|
8
8
|
tree = tree_connect(share) unless tree
|
9
|
-
named_pipe = tree.
|
9
|
+
named_pipe = tree.open_pipe(filename: "winreg", write: true, read: true)
|
10
10
|
if block_given?
|
11
11
|
res = yield named_pipe
|
12
12
|
named_pipe.close
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module Compression
|
3
|
+
module LZNT1
|
4
|
+
def self.compress(buf, chunk_size: 0x1000)
|
5
|
+
out = ''
|
6
|
+
until buf.empty?
|
7
|
+
chunk = buf[0...chunk_size]
|
8
|
+
compressed = compress_chunk(chunk)
|
9
|
+
# chunk is compressed
|
10
|
+
if compressed.length < chunk.length
|
11
|
+
out << [0xb000 | (compressed.length - 1)].pack('v')
|
12
|
+
out << compressed
|
13
|
+
else
|
14
|
+
out << [0x3000 | (chunk.length - 1)].pack('v')
|
15
|
+
out << chunk
|
16
|
+
end
|
17
|
+
buf = buf[chunk_size..-1]
|
18
|
+
break if buf.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
out
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.compress_chunk(chunk)
|
25
|
+
blob = chunk
|
26
|
+
out = ''
|
27
|
+
pow2 = 0x10
|
28
|
+
l_mask3 = 0x1002
|
29
|
+
o_shift = 12
|
30
|
+
until blob.empty?
|
31
|
+
bits = 0
|
32
|
+
tmp = ''
|
33
|
+
i = -1
|
34
|
+
loop do
|
35
|
+
i += 1
|
36
|
+
bits >>= 1
|
37
|
+
while pow2 < (chunk.length - blob.length)
|
38
|
+
pow2 <<= 1
|
39
|
+
l_mask3 = (l_mask3 >> 1) + 1
|
40
|
+
o_shift -= 1
|
41
|
+
end
|
42
|
+
|
43
|
+
max_len = [blob.length, l_mask3].min
|
44
|
+
offset, length = find(chunk[0...(chunk.length - blob.length)], blob, max_len)
|
45
|
+
|
46
|
+
# try to find more compressed pattern
|
47
|
+
_offset2, length2 = find(chunk[0...chunk.length - blob.length + 1], blob[1..-1], max_len)
|
48
|
+
|
49
|
+
length = 0 if length < length2
|
50
|
+
|
51
|
+
if length > 0
|
52
|
+
symbol = ((offset - 1) << o_shift) | (length - 3)
|
53
|
+
tmp << [symbol].pack('v')
|
54
|
+
# set the highest bit
|
55
|
+
bits |= 0x80
|
56
|
+
blob = blob[length..-1]
|
57
|
+
else
|
58
|
+
tmp += blob[0]
|
59
|
+
blob = blob[1..-1]
|
60
|
+
end
|
61
|
+
|
62
|
+
break if blob.empty? || i == 7
|
63
|
+
end
|
64
|
+
|
65
|
+
out << [bits >> (7 - i)].pack('C')
|
66
|
+
out << tmp
|
67
|
+
end
|
68
|
+
|
69
|
+
out
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.decompress(buf, length_check: true)
|
73
|
+
out = ''
|
74
|
+
until buf.empty?
|
75
|
+
header = buf.unpack1('v')
|
76
|
+
length = (header & 0xfff) + 1
|
77
|
+
raise EncodingError, 'invalid chunk length' if length_check && length > (buf.length - 2)
|
78
|
+
|
79
|
+
chunk = buf[2...length + 2]
|
80
|
+
out << if header & 0x8000 == 0
|
81
|
+
chunk
|
82
|
+
else
|
83
|
+
decompress_chunk(chunk)
|
84
|
+
end
|
85
|
+
buf = buf[length + 2..-1]
|
86
|
+
end
|
87
|
+
|
88
|
+
out
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.decompress_chunk(chunk)
|
92
|
+
out = ''
|
93
|
+
until chunk.empty?
|
94
|
+
flags = chunk[0].unpack1('C')
|
95
|
+
chunk = chunk[1..-1]
|
96
|
+
8.times do |i|
|
97
|
+
if (flags >> i & 1) == 0
|
98
|
+
out << chunk[0]
|
99
|
+
chunk = chunk[1..-1]
|
100
|
+
else
|
101
|
+
flag = chunk.unpack1('v')
|
102
|
+
pos = out.length - 1
|
103
|
+
l_mask = 0xfff
|
104
|
+
o_shift = 12
|
105
|
+
while pos >= 0x10
|
106
|
+
l_mask >>= 1
|
107
|
+
o_shift -= 1
|
108
|
+
pos >>= 1
|
109
|
+
end
|
110
|
+
|
111
|
+
length = (flag & l_mask) + 3
|
112
|
+
offset = (flag >> o_shift) + 1
|
113
|
+
|
114
|
+
if length >= offset
|
115
|
+
out_offset = out[-offset..-1]
|
116
|
+
tmp = out_offset * (0xfff / out_offset.length + 1)
|
117
|
+
out << tmp[0...length]
|
118
|
+
else
|
119
|
+
out << out[-offset..-offset + length - 1]
|
120
|
+
end
|
121
|
+
chunk = chunk[2..-1]
|
122
|
+
end
|
123
|
+
break if chunk.empty?
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
out
|
128
|
+
end
|
129
|
+
|
130
|
+
class << self
|
131
|
+
private
|
132
|
+
|
133
|
+
def find(src, target, max_len)
|
134
|
+
result_offset = 0
|
135
|
+
result_length = 0
|
136
|
+
1.upto(max_len - 1) do |i|
|
137
|
+
offset = src.rindex(target[0...i])
|
138
|
+
next if offset.nil?
|
139
|
+
|
140
|
+
tmp_offset = src.length - offset
|
141
|
+
tmp_length = i
|
142
|
+
if tmp_offset == tmp_length
|
143
|
+
src_offset = src[offset..-1]
|
144
|
+
tmp = src_offset * (0xfff / src_offset.length + 1)
|
145
|
+
i.upto(max_len) do |j|
|
146
|
+
offset = tmp.rindex(target[0...j])
|
147
|
+
break if offset.nil?
|
148
|
+
|
149
|
+
tmp_length = j
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
if tmp_length > result_length
|
154
|
+
result_offset = tmp_offset
|
155
|
+
result_length = tmp_length
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
result_length < 3 ? [0, 0] : [result_offset, result_length]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|