byteboozer2 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ff05a3ba454e03b5bb263f96bae094a8f1dbeb11
4
+ data.tar.gz: ed570a340f698b625493d14dbdf90c452352565d
5
+ SHA512:
6
+ metadata.gz: 5a026071154ecd1351fe99a05fe100baa9e27e96d2680b03d20f73dea17c2569a87135911d20d75edcff8b7f3ab06d8bf3e331066078fa6409de6f258773ca6a
7
+ data.tar.gz: c6345de354bf6e928e91cfad8b76a1a5ac51a3b1a4a0359af370c49182b13a167fb8f6ee5cea9ce7fae09ca5bf9fcfa635605aa5d4e0032705ad2c7601c71f15
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ /byteboozer2.log
2
+ /pkg/
data/.rubocop.yml ADDED
@@ -0,0 +1,16 @@
1
+ Metrics/AbcSize:
2
+ Max: 174.1
3
+ Metrics/BlockNesting:
4
+ Max: 8
5
+ Metrics/ClassLength:
6
+ Max: 472
7
+ Metrics/CyclomaticComplexity:
8
+ Max: 46
9
+ Metrics/LineLength:
10
+ Max: 120
11
+ Metrics/MethodLength:
12
+ Max: 116
13
+ Metrics/PerceivedComplexity:
14
+ Max: 48
15
+ Style/ZeroLengthPredicate:
16
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,55 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ byteboozer2 (0.0.1)
5
+ activemodel (~> 4.2.6)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (4.2.6)
11
+ activesupport (= 4.2.6)
12
+ builder (~> 3.1)
13
+ activesupport (4.2.6)
14
+ i18n (~> 0.7)
15
+ json (~> 1.7, >= 1.7.7)
16
+ minitest (~> 5.1)
17
+ thread_safe (~> 0.3, >= 0.3.4)
18
+ tzinfo (~> 1.1)
19
+ ast (2.2.0)
20
+ awesome_print (1.6.1)
21
+ builder (3.2.2)
22
+ i18n (0.7.0)
23
+ json (1.8.3)
24
+ minitest (5.8.4)
25
+ parser (2.3.0.7)
26
+ ast (~> 2.2)
27
+ powerpack (0.1.1)
28
+ rainbow (2.1.0)
29
+ rake (11.1.1)
30
+ rubocop (0.38.0)
31
+ parser (>= 2.3.0.6, < 3.0)
32
+ powerpack (~> 0.1)
33
+ rainbow (>= 1.99.1, < 3.0)
34
+ ruby-progressbar (~> 1.7)
35
+ unicode-display_width (~> 1.0, >= 1.0.1)
36
+ ruby-progressbar (1.7.5)
37
+ thread_safe (0.3.5)
38
+ tzinfo (1.2.2)
39
+ thread_safe (~> 0.1)
40
+ unicode-display_width (1.0.2)
41
+
42
+ PLATFORMS
43
+ ruby
44
+
45
+ DEPENDENCIES
46
+ awesome_print (~> 1.6.1)
47
+ bundler (~> 1.11.2)
48
+ byteboozer2!
49
+ json (~> 1.8.3)
50
+ minitest (~> 5.8.4)
51
+ rake (~> 11.1.1)
52
+ rubocop (~> 0.38.0)
53
+
54
+ BUNDLED WITH
55
+ 1.11.2
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (C) 2016 Pawel Krol
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # ByteBoozer2
2
+
3
+ `ByteBoozer2` package provides a native Ruby port of David Malmborg's [ByteBoozer 2.0](http://csdb.dk/release/?id=145031), a data cruncher for Commodore files written in C.
4
+
5
+ ## Version
6
+
7
+ Version 0.01 (2016-03-28)
8
+
9
+ ## Description
10
+
11
+ `ByteBoozer 2.0` is very much the same as `ByteBoozer 1.0`, but it generates smaller files and decrunches at about 2x the speed. An additional effort was put into keeping the encoder at about the same speed as before. Obviously it is incompatible with the version 1.0.
12
+
13
+ Compressed data is by default written into a file named with `.b2` suffix. Target file must not exist. If you want an executable, use `ecrunch`. If you want to decrunch yourself, use `crunch` or `rcrunch`. The decruncher should be called with `X` and `Y` registers loaded with a hi- and lo-byte address of the crunched file in a memory.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'byteboozer2', '~> 0.0.1'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle install
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install byteboozer2
30
+
31
+ ## Usage
32
+
33
+ The following operations are supported: crunching files, crunching files and making an executable with start address `$xxxx`, crunching files and relocating data to hex address `$xxxx`.
34
+
35
+ require 'byteboozer2'
36
+ include ByteBoozer2
37
+
38
+ # Crunch file:
39
+ crunch(file_name)
40
+
41
+ # Crunch file and make executable with start address $xxxx:
42
+ ecrunch(file_name, address)
43
+
44
+ # Crunch file and relocate data to hex address $xxxx:
45
+ rcrunch(file_name, address)
46
+
47
+ ## Development
48
+
49
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `rake install`. To release a new version, update the version number in `version.rb`, and then run `rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
50
+
51
+ ## Contributing
52
+
53
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/pawelkrol/) at [https://github.com/pawelkrol/byteboozer2_ruby](https://github.com/pawelkrol/byteboozer2_ruby).
54
+
55
+ ## License
56
+
57
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ require 'rubygems/package_task'
11
+ spec = Gem::Specification.load(File.expand_path('../byteboozer2.gemspec', __FILE__))
12
+ Gem::PackageTask.new(spec).define
13
+
14
+ require 'rubocop/rake_task'
15
+ RuboCop::RakeTask.new
16
+
17
+ task default: [:rubocop, :test]
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'byteboozer2'
5
+
6
+ require 'irb'
7
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'byteboozer2/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'byteboozer2'
8
+ spec.version = ByteBoozer2::VERSION
9
+ spec.authors = ['Pawel Krol']
10
+ spec.email = ['djgruby@gmail.com']
11
+
12
+ spec.summary = 'A data cruncher for Commodore files written in pure Ruby'
13
+ spec.description = 'This is a native Ruby port of David Malmborg\'s ByteBoozer 2.0.'
14
+ spec.homepage = 'https://github.com/pawelkrol/byteboozer2_ruby'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_development_dependency 'awesome_print', '~> 1.6.1'
23
+ spec.add_development_dependency 'bundler', '~> 1.11.2'
24
+ spec.add_development_dependency 'json', '~> 1.8.3'
25
+ spec.add_development_dependency 'minitest', '~> 5.8.4'
26
+ spec.add_development_dependency 'rake', '~> 11.1.1'
27
+ spec.add_development_dependency 'rubocop', '~> 0.38.0'
28
+
29
+ spec.add_runtime_dependency 'activemodel', '~> 4.2.6'
30
+ end
@@ -0,0 +1,650 @@
1
+ require 'active_model'
2
+ require 'ostruct'
3
+
4
+ module ByteBoozer2
5
+ # This class implements ByteBoozer's 2.0 crunching algorithm.
6
+ class Cruncher
7
+ include ActiveModel::Validations
8
+
9
+ attr_reader :address, :result
10
+
11
+ validates_numericality_of :address, only_integer: true,
12
+ allow_nil: true,
13
+ greater_than_or_equal_to: 0x0000,
14
+ less_than_or_equal_to: 0xffff
15
+
16
+ def self.crunch(*args)
17
+ new(*args).crunch
18
+ end
19
+
20
+ def initialize(data, options = {})
21
+ @data = data
22
+ @executable = options[:executable] || false
23
+ @relocated = options[:relocated] || false
24
+ @address = options[:address] || 0x0000
25
+
26
+ raise ArgumentError unless valid?
27
+ end
28
+
29
+ def crunch!
30
+ @ibuf_size = @data.length - 2
31
+
32
+ # Load ibuf and clear context
33
+ @ibuf = @data[2..-1]
34
+ @context = Array.new(@ibuf_size) { new_node }
35
+ @link = Array.new(@ibuf_size) { 0 }
36
+ @rle_info = Array.new(@ibuf_size) { OpenStruct.new(value: 0, value_after: 0, length: 0) }
37
+
38
+ setup_help_structures
39
+ find_matches
40
+ @obuf = Array.new(MEM_SIZE) { 0 }
41
+ margin = write_output
42
+
43
+ pack_len = @put
44
+ file_len = @put
45
+ decr_len = 0
46
+ if @executable
47
+ decr_len = DECRUNCHER_LENGTH
48
+ file_len += decr_len + 2
49
+ else
50
+ file_len += 4
51
+ end
52
+
53
+ @result = Array.new(file_len) { 0 }
54
+
55
+ if @executable
56
+ start_address = 0x10000 - pack_len
57
+ transf_address = file_len + 0x6ff
58
+
59
+ decr_code[0x1f] = transf_address & 0xff # Transfer from...
60
+ decr_code[0x20] = transf_address >> 8
61
+ decr_code[0xbc] = start_address & 0xff # Depack from...
62
+ decr_code[0xbd] = start_address >> 8
63
+ decr_code[0x85] = @data[0] # Depack to...
64
+ decr_code[0x86] = @data[1]
65
+ decr_code[0xca] = @address & 0xff # Jump to...
66
+ decr_code[0xcb] = @address >> 8
67
+
68
+ @result[0] = 0x01
69
+ @result[1] = 0x08
70
+
71
+ @result[2, decr_len] = decr_code
72
+
73
+ @result[2 + decr_len, @put] = @obuf[0, @put]
74
+ else # Not executable...
75
+ # Experimantal decision of start address
76
+ # start_address = 0xfffa - pack_len - 2
77
+ start_address = (@data[1] << 8) | @data[0]
78
+ start_address += (@ibuf_size - pack_len - 2 + margin)
79
+
80
+ start_address = @address - pack_len - 2 if @relocated
81
+
82
+ @result[0] = start_address & 0xff # Load address
83
+ @result[1] = start_address >> 8
84
+ @result[2] = @data[0] # Depack to address
85
+ @result[3] = @data[1]
86
+
87
+ @result[4, @put] = @obuf[0, @put]
88
+ end
89
+
90
+ true
91
+ end
92
+
93
+ def crunch
94
+ @result if crunch!
95
+ end
96
+
97
+ private
98
+
99
+ DECRUNCHER = [
100
+ 0x0b, 0x08, 0x00, 0x00, 0x9e, 0x32, 0x30, 0x36, 0x31, 0x00, 0x00, 0x00, 0x78, 0xa9, 0x34, 0x85,
101
+ 0x01, 0xa2, 0xb7, 0xbd, 0x1e, 0x08, 0x95, 0x0f, 0xca, 0xd0, 0xf8, 0x4c, 0x10, 0x00, 0xbd, 0xd6,
102
+ 0x07, 0x9d, 0x00, 0xff, 0xe8, 0xd0, 0xf7, 0xc6, 0x12, 0xc6, 0x15, 0xa5, 0x12, 0xc9, 0x07, 0xb0,
103
+ 0xed, 0x20, 0xa0, 0x00, 0xb0, 0x17, 0x20, 0x8e, 0x00, 0x85, 0x36, 0xa0, 0x00, 0x20, 0xad, 0x00,
104
+ 0x91, 0x77, 0xc8, 0xc0, 0x00, 0xd0, 0xf6, 0x20, 0x83, 0x00, 0xc8, 0xf0, 0xe4, 0x20, 0x8e, 0x00,
105
+ 0xaa, 0xe8, 0xf0, 0x71, 0x86, 0x7b, 0xa9, 0x00, 0xe0, 0x03, 0x2a, 0x20, 0x9b, 0x00, 0x20, 0x9b,
106
+ 0x00, 0xaa, 0xb5, 0xbf, 0xf0, 0x07, 0x20, 0x9b, 0x00, 0xb0, 0xfb, 0x30, 0x07, 0x49, 0xff, 0xa8,
107
+ 0x20, 0xad, 0x00, 0xae, 0xa0, 0xff, 0x65, 0x77, 0x85, 0x74, 0x98, 0x65, 0x78, 0x85, 0x75, 0xa0,
108
+ 0x00, 0xb9, 0xad, 0xde, 0x99, 0x00, 0x00, 0xc8, 0xc0, 0x00, 0xd0, 0xf5, 0x20, 0x83, 0x00, 0xd0,
109
+ 0xa0, 0x18, 0x98, 0x65, 0x77, 0x85, 0x77, 0x90, 0x02, 0xe6, 0x78, 0x60, 0xa9, 0x01, 0x20, 0xa0,
110
+ 0x00, 0x90, 0x05, 0x20, 0x9b, 0x00, 0x10, 0xf6, 0x60, 0x20, 0xa0, 0x00, 0x2a, 0x60, 0x06, 0xbe,
111
+ 0xd0, 0x08, 0x48, 0x20, 0xad, 0x00, 0x2a, 0x85, 0xbe, 0x68, 0x60, 0xad, 0xed, 0xfe, 0xe6, 0xae,
112
+ 0xd0, 0x02, 0xe6, 0xaf, 0x60, 0xa9, 0x37, 0x85, 0x01, 0x4c, 0x00, 0x00, 0x80, 0xdf, 0xfb, 0x00,
113
+ 0x80, 0xef, 0xfd, 0x80, 0xf0
114
+ ].freeze
115
+
116
+ DECRUNCHER_LENGTH = DECRUNCHER.length
117
+ MEM_SIZE = 0x10000
118
+
119
+ NUM_BITS_SHORT_0 = 3
120
+ NUM_BITS_SHORT_1 = 6
121
+ NUM_BITS_SHORT_2 = 8
122
+ NUM_BITS_SHORT_3 = 10
123
+ NUM_BITS_LONG_0 = 4
124
+ NUM_BITS_LONG_1 = 7
125
+ NUM_BITS_LONG_2 = 10
126
+ NUM_BITS_LONG_3 = 13
127
+
128
+ LEN_SHORT_0 = 1 << NUM_BITS_SHORT_0
129
+ LEN_SHORT_1 = 1 << NUM_BITS_SHORT_1
130
+ LEN_SHORT_2 = 1 << NUM_BITS_SHORT_2
131
+ LEN_SHORT_3 = 1 << NUM_BITS_SHORT_3
132
+ LEN_LONG_0 = 1 << NUM_BITS_LONG_0
133
+ LEN_LONG_1 = 1 << NUM_BITS_LONG_1
134
+ LEN_LONG_2 = 1 << NUM_BITS_LONG_2
135
+ LEN_LONG_3 = 1 << NUM_BITS_LONG_3
136
+
137
+ MAX_OFFSET = LEN_LONG_3
138
+ MAX_OFFSET_SHORT = LEN_SHORT_3
139
+
140
+ def cost_of_length(len)
141
+ if len == 1
142
+ 1
143
+ elsif len >= 2 && len <= 3
144
+ 3
145
+ elsif len >= 4 && len <= 7
146
+ 5
147
+ elsif len >= 8 && len <= 15
148
+ 7
149
+ elsif len >= 16 && len <= 31
150
+ 9
151
+ elsif len >= 32 && len <= 63
152
+ 11
153
+ elsif len >= 64 && len <= 127
154
+ 13
155
+ elsif len >= 128 && len <= 255
156
+ 14
157
+ else
158
+ ByteBoozer2.logger.warn 'cost_of_length got wrong value: #{len}'
159
+ 10_000
160
+ end
161
+ end
162
+
163
+ def calculate_cost_of_literal(old_cost, lit_len)
164
+ new_cost = old_cost + 8
165
+
166
+ # FIXME, what if lit_len > 255?
167
+ #
168
+ # FIXME, cost model for literals does not work
169
+ # Quick wins on short matches are prioritized before a longer
170
+ # literal run, which in the end results in a worse result
171
+ # Most obvious on files hard to crunch
172
+ case lit_len
173
+ when 1 then new_cost += 1
174
+ when 128 then new_cost += 1
175
+ when 2 then new_cost += 2
176
+ when 4 then new_cost += 2
177
+ when 8 then new_cost += 2
178
+ when 16 then new_cost += 2
179
+ when 32 then new_cost += 2
180
+ when 64 then new_cost += 2
181
+ end
182
+
183
+ new_cost
184
+ end
185
+
186
+ def calculate_cost_of_match(len, offset)
187
+ cost = 1 # Copy-bit
188
+ cost += cost_of_length(len - 1)
189
+ cost += 2 # num offset bits
190
+ cost += cost_of_offset(offset - 1, len - 1)
191
+ cost
192
+ end
193
+
194
+ def cost_of_offset(offset, len)
195
+ if len == 1
196
+ return NUM_BITS_SHORT_0 if cond_short_0(offset)
197
+ return NUM_BITS_SHORT_1 if cond_short_1(offset)
198
+ return NUM_BITS_SHORT_2 if cond_short_2(offset)
199
+ return NUM_BITS_SHORT_3 if cond_short_3(offset)
200
+ else
201
+ return NUM_BITS_LONG_0 if cond_long_0(offset)
202
+ return NUM_BITS_LONG_1 if cond_long_1(offset)
203
+ return NUM_BITS_LONG_2 if cond_long_2(offset)
204
+ return NUM_BITS_LONG_3 if cond_long_3(offset)
205
+ end
206
+
207
+ ByteBoozer2.logger.warn 'cost_of_offset got wrong offset: #{offset}'
208
+ 10_000
209
+ end
210
+
211
+ def cond_short_0(o)
212
+ o >= 0 && o < LEN_SHORT_0
213
+ end
214
+
215
+ def cond_short_1(o)
216
+ o >= LEN_SHORT_0 && o < LEN_SHORT_1
217
+ end
218
+
219
+ def cond_short_2(o)
220
+ o >= LEN_SHORT_1 && o < LEN_SHORT_2
221
+ end
222
+
223
+ def cond_short_3(o)
224
+ o >= LEN_SHORT_2 && o < LEN_SHORT_3
225
+ end
226
+
227
+ def cond_long_0(o)
228
+ o >= 0 && o < LEN_LONG_0
229
+ end
230
+
231
+ def cond_long_1(o)
232
+ o >= LEN_LONG_0 && o < LEN_LONG_1
233
+ end
234
+
235
+ def cond_long_2(o)
236
+ o >= LEN_LONG_1 && o < LEN_LONG_2
237
+ end
238
+
239
+ def cond_long_3(o)
240
+ o >= LEN_LONG_2 && o < LEN_LONG_3
241
+ end
242
+
243
+ def decr_code
244
+ @decr_code ||= DECRUNCHER.dup
245
+ end
246
+
247
+ def find_matches
248
+ matches = Array.new(256) { OpenStruct.new(length: 0, offset: 0) }
249
+
250
+ last_node = new_node
251
+
252
+ get = @ibuf_size - 1
253
+ cur = @ibuf[get]
254
+
255
+ while get >= 0
256
+ # Clear matches for current position
257
+ matches.each do |match|
258
+ match.length = 0
259
+ match.offset = 0
260
+ end
261
+
262
+ cur = (cur << 8) & 0xffff # Table 65536 lookup
263
+ cur |= @ibuf[get - 1] if get > 0
264
+ scn = @first[cur]
265
+ scn = @link[scn]
266
+
267
+ longest_match = 0
268
+
269
+ if @rle_info[get].length == 0 # No RLE-match here...
270
+ # Scan until start of file, or max offset
271
+ while get - scn <= MAX_OFFSET && scn > 0 && longest_match < 255
272
+ # OK, we have a match of length 2 or longer, but max 255 or file start
273
+ len = 2
274
+ while len < 255 && scn >= len && @ibuf[scn - len] == @ibuf[get - len]
275
+ len += 1
276
+ end
277
+
278
+ # Calc offset
279
+ offset = get - scn
280
+
281
+ # Store match only if it's the longest so far
282
+ if len > longest_match
283
+ longest_match = len
284
+
285
+ # Store the match only if first (= best) of this length
286
+ while len >= 2 && matches[len].length == 0
287
+ # If len == 2, check against short offset!
288
+ if len > 2 || (len == 2 && offset <= MAX_OFFSET_SHORT)
289
+ matches[len].length = len
290
+ matches[len].offset = get - scn
291
+ end
292
+
293
+ len -= 1
294
+ end
295
+ end
296
+
297
+ scn = @link[scn] # Table 65535 lookup
298
+ end
299
+
300
+ @first[cur] = @link[@first[cur]] # Waste first entry
301
+ else # if RLE-match...
302
+ rle_len = @rle_info[get].length
303
+ rle_val_after = @rle_info[get].value_after
304
+
305
+ # First match with self-RLE, which is always one byte shorter than the RLE itself
306
+ len = rle_len - 1
307
+ if len > 1
308
+ len = 255 if len > 255
309
+ longest_match = len
310
+
311
+ # Store the match
312
+ while len >= 2
313
+ matches[len].length = len
314
+ matches[len].offset = 1
315
+
316
+ len -= 1
317
+ end
318
+ end
319
+
320
+ # Search for more RLE-matches, scan until start of file, or max offset...
321
+ while get - scn <= MAX_OFFSET && scn > 0 && longest_match < 255
322
+
323
+ # Check for longer matches with same value and after...
324
+ # FIXME: That is not what it does, is it?!
325
+ if @rle_info[scn].length > longest_match && rle_len > longest_match
326
+ offset = get - scn
327
+ len = @rle_info[scn].length
328
+
329
+ len = rle_len if len > rle_len
330
+
331
+ if len > 2 || (len == 2 && offset <= MAX_OFFSET_SHORT)
332
+ matches[len].length = len
333
+ matches[len].offset = offset
334
+
335
+ longest_match = len
336
+ end
337
+ end
338
+
339
+ # Check for matches beyond the RLE...
340
+ if @rle_info[scn].length >= rle_len && @rle_info[scn].value_after == rle_val_after
341
+
342
+ # Here is a match that goes beyond the RLE...
343
+ # Find out correct offset to use value_after, then search further to see if more bytes equal
344
+ len = rle_len
345
+ offset = get - scn + @rle_info[scn].length - rle_len
346
+
347
+ if offset <= MAX_OFFSET
348
+ while len < 255 && get >= offset + len && @ibuf[get - offset - len] == @ibuf[get - len]
349
+ len += 1
350
+ end
351
+
352
+ if len > longest_match
353
+ longest_match = len
354
+
355
+ # Store the match only if first (= best) of this length
356
+ while len >= 2 && matches[len].length == 0
357
+ # If len == 2, check against short offset!
358
+ if len > 2 || (len == 2 && offset <= MAX_OFFSET_SHORT)
359
+ matches[len].length = len
360
+ matches[len].offset = offset
361
+ end
362
+
363
+ len -= 1
364
+ end
365
+ end
366
+ end
367
+ end
368
+
369
+ scn = @link[scn] # Table 65535 lookup
370
+ end
371
+
372
+ if @rle_info[get].length > 2
373
+ # Expand RLE to next position
374
+ @rle_info[get - 1].length = @rle_info[get].length - 1
375
+ @rle_info[get - 1].value = @rle_info[get].value
376
+ @rle_info[get - 1].value_after = @rle_info[get].value_after
377
+ else
378
+ # End of RLE, advance link
379
+ @first[cur] = @link[@first[cur]] # Waste first entry
380
+ end
381
+ end
382
+
383
+ # Now that we have all matches from this position, visit all nodes reached by the matches
384
+ 255.downto(1).to_a.each do |i|
385
+ # Find all matches we stored
386
+ len = matches[i].length
387
+ offset = matches[i].offset
388
+
389
+ next if len == 0
390
+ target_i = get - len + 1
391
+ target = @context[target_i]
392
+
393
+ # Calculate cost for this jump
394
+ current_cost = last_node.cost
395
+ current_cost += calculate_cost_of_match(len, offset)
396
+
397
+ # If this match is first or cheapest way to get here, then update node
398
+ next if target.cost != 0 && target.cost <= current_cost
399
+ target.cost = current_cost
400
+ target.next = get + 1
401
+ target.lit_len = 0
402
+ target.offset = offset
403
+ end
404
+
405
+ # Calc the cost for this node if using one more literal
406
+ lit_len = last_node.lit_len + 1
407
+ lit_cost = calculate_cost_of_literal(last_node.cost, lit_len)
408
+
409
+ # If literal run is first or cheapest way to get here, then update node
410
+ this = @context[get]
411
+ if this.cost == 0 || this.cost >= lit_cost
412
+ this.cost = lit_cost
413
+ this.next = get + 1
414
+ this.lit_len = lit_len
415
+ end
416
+
417
+ last_node.cost = this.cost
418
+ last_node.next = this.next
419
+ last_node.lit_len = this.lit_len
420
+
421
+ # Loop to the next position
422
+ get -= 1
423
+ end
424
+ end
425
+
426
+ def new_node
427
+ OpenStruct.new(cost: 0, next: 0, lit_len: 0, offset: 0)
428
+ end
429
+
430
+ def setup_help_structures
431
+ # Setup RLE-info
432
+ get = @ibuf_size - 1
433
+ while get > 0
434
+
435
+ cur = @ibuf[get]
436
+ if cur == @ibuf[get - 1]
437
+
438
+ len = 2
439
+ len += 1 while get >= len && cur == @ibuf[get - len]
440
+
441
+ @rle_info[get].length = len
442
+ @rle_info[get].value_after = get >= len ? @ibuf[get - len] : cur # Avoid accessing @ibuf[-1]
443
+
444
+ get -= len
445
+ else
446
+ get -= 1
447
+ end
448
+ end
449
+
450
+ # Setup linked list
451
+ @first = Array.new(MEM_SIZE) { 0 }
452
+ @last = Array.new(MEM_SIZE) { 0 }
453
+
454
+ get = @ibuf_size - 1
455
+ cur = @ibuf[get]
456
+
457
+ while get > 0
458
+ cur = ((cur << 8) | @ibuf[get - 1]) & 0xffff
459
+
460
+ if @first[cur] == 0
461
+ @first[cur] = @last[cur] = get
462
+ else
463
+ @link[@last[cur]] = get
464
+ @last[cur] = get
465
+ end
466
+
467
+ get -= @rle_info[get].length == 0 ? 1 : @rle_info[get].length - 1 # if RLE-match...
468
+ end
469
+ end
470
+
471
+ def wbit(bit)
472
+ if @cur_cnt == 0
473
+ @obuf[@cur_index] = @cur_byte
474
+ @cur_index = @put
475
+ @cur_cnt = 8
476
+ @cur_byte = 0
477
+ @put += 1
478
+ end
479
+
480
+ @cur_byte <<= 1
481
+ @cur_byte |= bit & 1
482
+ @cur_cnt -= 1
483
+ end
484
+
485
+ def wbyte(b)
486
+ @obuf[@put] = b
487
+ @put += 1
488
+ end
489
+
490
+ def wbytes(get, len)
491
+ (0..len - 1).each do
492
+ wbyte(@ibuf[get])
493
+ get += 1
494
+ end
495
+ end
496
+
497
+ def wflush
498
+ while @cur_cnt != 0
499
+ @cur_byte <<= 1
500
+ @cur_cnt -= 1
501
+ end
502
+ @obuf[@cur_index] = @cur_byte
503
+ end
504
+
505
+ def wlength(len)
506
+ # return if len == 0 # Should never happen
507
+
508
+ bit = 0x80
509
+ bit >>= 1 while len & bit == 0
510
+
511
+ while bit > 1
512
+ wbit(1)
513
+ bit >>= 1
514
+ wbit(len & bit == 0 ? 0 : 1)
515
+ end
516
+
517
+ wbit(0) if len < 0x80
518
+ end
519
+
520
+ def woffset(offset, len)
521
+ i = 0
522
+ n = 0
523
+
524
+ if len == 1
525
+ if cond_short_0(offset)
526
+ i = 0
527
+ n = NUM_BITS_SHORT_0
528
+ end
529
+ if cond_short_1(offset)
530
+ i = 1
531
+ n = NUM_BITS_SHORT_1
532
+ end
533
+ if cond_short_2(offset)
534
+ i = 2
535
+ n = NUM_BITS_SHORT_2
536
+ end
537
+ if cond_short_3(offset)
538
+ i = 3
539
+ n = NUM_BITS_SHORT_3
540
+ end
541
+ else
542
+ if cond_long_0(offset)
543
+ i = 0
544
+ n = NUM_BITS_LONG_0
545
+ end
546
+ if cond_long_1(offset)
547
+ i = 1
548
+ n = NUM_BITS_LONG_1
549
+ end
550
+ if cond_long_2(offset)
551
+ i = 2
552
+ n = NUM_BITS_LONG_2
553
+ end
554
+ if cond_long_3(offset)
555
+ i = 3
556
+ n = NUM_BITS_LONG_3
557
+ end
558
+ end
559
+
560
+ # First write number of bits
561
+ wbit(i & 2 == 0 ? 0 : 1)
562
+ wbit(i & 1 == 0 ? 0 : 1)
563
+
564
+ if n >= 8 # Offset is 2 bytes
565
+
566
+ # Then write the bits less than 8
567
+ b = 1 << n
568
+ while b > 0x100
569
+ b >>= 1
570
+ wbit(b & offset == 0 ? 0 : 1)
571
+ end
572
+
573
+ # Finally write a whole byte, if necessary
574
+ wbyte(offset & 255 ^ 255) # Inverted (!)
575
+ offset >>= 8
576
+
577
+ else # Offset is 1 byte
578
+
579
+ # Then write the bits less than 8
580
+ b = 1 << n
581
+ while b > 1
582
+ b >>= 1
583
+ wbit(b & offset == 0 ? 1 : 0) # Inverted (!)
584
+ end
585
+ end
586
+ end
587
+
588
+ def write_output
589
+ @put = 0
590
+
591
+ @cur_byte = 0
592
+ @cur_cnt = 8
593
+ @cur_index = @put
594
+ @put += 1
595
+
596
+ max_diff = 0
597
+
598
+ need_copy_bit = true
599
+
600
+ i = 0
601
+ while i < @ibuf_size
602
+ link = @context[i].next
603
+ # cost = @context[i].cost
604
+ lit_len = @context[i].lit_len
605
+ offset = @context[i].offset
606
+
607
+ if lit_len == 0
608
+ # Put match
609
+ len = link - i
610
+
611
+ ByteBoozer2.logger.debug format('$%04x: Mat(%i, %i)', i, len, offset)
612
+
613
+ wbit(1) if need_copy_bit
614
+ wlength(len - 1)
615
+ woffset(offset - 1, len - 1)
616
+
617
+ i = link
618
+
619
+ need_copy_bit = true
620
+ else
621
+ # Put literal
622
+ need_copy_bit = false
623
+
624
+ while lit_len > 0
625
+ len = lit_len < 255 ? lit_len : 255
626
+
627
+ ByteBoozer2.logger.debug format('$%04x: Lit(%i)', i, len)
628
+
629
+ wbit(0)
630
+ wlength(len)
631
+ wbytes(i, len)
632
+
633
+ need_copy_bit = true if lit_len == 255
634
+
635
+ lit_len -= len
636
+ i += len
637
+ end
638
+ end
639
+
640
+ max_diff = i - @put if i - @put > max_diff
641
+ end
642
+
643
+ wbit(1)
644
+ wlength(0xff)
645
+ wflush
646
+
647
+ max_diff - i + @put
648
+ end
649
+ end
650
+ end
@@ -0,0 +1,29 @@
1
+ module ByteBoozer2
2
+ # This class implements file handling related helper methods.
3
+ class File
4
+ attr_accessor :data, :name
5
+
6
+ def self.load(*args)
7
+ new(*args).tap(&:read)
8
+ end
9
+
10
+ def self.save(*args)
11
+ new(*args).tap(&:write)
12
+ end
13
+
14
+ def initialize(name, data = nil)
15
+ @name = name
16
+ @data = data
17
+ end
18
+
19
+ def read
20
+ @data = IO.binread(@name).unpack('C*')
21
+ end
22
+
23
+ def write
24
+ ::File.open(@name, ::File::WRONLY | ::File::CREAT | ::File::EXCL, binmode: true, encoding: 'ASCII-8BIT') do |file|
25
+ file.write @data.pack('C*')
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module ByteBoozer2
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,38 @@
1
+ require 'byteboozer2/cruncher'
2
+ require 'byteboozer2/file'
3
+ require 'byteboozer2/version'
4
+ require 'logger'
5
+
6
+ # This module provides compression methods available in ByteBoozer 2.0.
7
+ module ByteBoozer2
8
+ def crunch(file_name)
9
+ compress(file_name)
10
+ end
11
+
12
+ def ecrunch(file_name, address)
13
+ compress(file_name, address: address, executable: true)
14
+ end
15
+
16
+ def self.logger
17
+ @logger ||= Logger.new('byteboozer2.log').tap do |log|
18
+ log.level = Logger::DEBUG
19
+ log.progname = 'ByteBoozer2'
20
+ end
21
+ end
22
+
23
+ def self.log_level=(level)
24
+ logger.level = level
25
+ end
26
+
27
+ def rcrunch(file_name, address)
28
+ compress(file_name, address: address, relocated: true)
29
+ end
30
+
31
+ private
32
+
33
+ def compress(file_name, *options)
34
+ file = ByteBoozer2::File.load(file_name)
35
+ result = ByteBoozer2::Cruncher.crunch(file.data, *options)
36
+ ByteBoozer2::File.save(file_name + '.b2', result)
37
+ end
38
+ end
@@ -0,0 +1,160 @@
1
+ ; ByteBoozer 2.0 Decruncher (C) 2014 David Malmborg
2
+
3
+ ; call: Y = AddrLo
4
+ ; X = AddrHi
5
+
6
+ ;Variables.. #Bytes
7
+ zp_base = $02 ; -
8
+ bits = zp_base ;1
9
+ put = zp_base+2 ;2
10
+
11
+ #macro GetNextBit() {.(
12
+ asl bits
13
+ bne DgEnd
14
+ jsr GetNewBits
15
+ DgEnd
16
+ .)}
17
+
18
+ #macro GetLen() {.(
19
+ lda #1
20
+ GlLoop
21
+ .GetNextBit()
22
+ bcc GlEnd
23
+ .GetNextBit()
24
+ rol
25
+ bpl GlLoop
26
+ GlEnd
27
+ .)}
28
+
29
+ Decrunch
30
+ sty Get1+1
31
+ sty Get2+1
32
+ sty Get3+1
33
+ stx Get1+2
34
+ stx Get2+2
35
+ stx Get3+2
36
+
37
+ ldx #0
38
+ jsr GetNewBits
39
+ sty put-1,x
40
+ cpx #2
41
+ bcc *-7
42
+ lda #$80
43
+ sta bits
44
+ DLoop
45
+ .GetNextBit()
46
+ bcs Match
47
+ Literal
48
+ ; Literal run.. get length.
49
+ .GetLen()
50
+ sta LLen+1
51
+
52
+ ldy #0
53
+ LLoop
54
+ Get3 lda $feed,x
55
+ inx
56
+ bne *+5
57
+ jsr GnbInc
58
+ L1 sta (put),y
59
+ iny
60
+ LLen cpy #0
61
+ bne LLoop
62
+
63
+ clc
64
+ tya
65
+ adc put
66
+ sta put
67
+ bcc *+4
68
+ inc put+1
69
+
70
+ iny
71
+ beq DLoop
72
+
73
+ ; Has to continue with a match..
74
+
75
+ Match
76
+ ; Match.. get length.
77
+ .GetLen()
78
+ sta MLen+1
79
+
80
+ ; Length 255 -> EOF
81
+ cmp #$ff
82
+ beq End
83
+
84
+ ; Get num bits
85
+ cmp #2
86
+ lda #0
87
+ rol
88
+ .GetNextBit()
89
+ rol
90
+ .GetNextBit()
91
+ rol
92
+ tay
93
+ lda Tab,y
94
+ beq M8
95
+
96
+ ; Get bits < 8
97
+ M_1 .GetNextBit()
98
+ rol
99
+ bcs M_1
100
+ bmi MShort
101
+ M8
102
+ ; Get byte
103
+ eor #$ff
104
+ tay
105
+ Get2 lda $feed,x
106
+ inx
107
+ bne *+5
108
+ jsr GnbInc
109
+ jmp Mdone
110
+ MShort
111
+ ldy #$ff
112
+ Mdone
113
+ ;clc
114
+ adc put
115
+ sta MLda+1
116
+ tya
117
+ adc put+1
118
+ sta MLda+2
119
+
120
+ ldy #$ff
121
+ MLoop iny
122
+ MLda lda $beef,y
123
+ sta (put),y
124
+ MLen cpy #0
125
+ bne MLoop
126
+
127
+ ;sec
128
+ tya
129
+ adc put
130
+ sta put
131
+ bcc *+4
132
+ inc put+1
133
+
134
+ jmp DLoop
135
+
136
+ End rts
137
+
138
+ GetNewBits
139
+ Get1 ldy $feed,x
140
+ sty bits
141
+ rol bits
142
+ inx
143
+ bne GnbEnd
144
+ GnbInc inc Get1+2
145
+ inc Get2+2
146
+ inc Get3+2
147
+ GnbEnd
148
+ rts
149
+
150
+ Tab
151
+ ; Short offsets
152
+ .byte %11011111 ; 3
153
+ .byte %11111011 ; 6
154
+ .byte %00000000 ; 8
155
+ .byte %10000000 ; 10
156
+ ; Long offsets
157
+ .byte %11101111 ; 4
158
+ .byte %11111101 ; 7
159
+ .byte %10000000 ; 10
160
+ .byte %11110000 ; 13
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: byteboozer2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Pawel Krol
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-03-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: awesome_print
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.6.1
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.6.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.11.2
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.11.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.8.3
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.8.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 5.8.4
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 5.8.4
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 11.1.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 11.1.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.38.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.38.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: activemodel
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 4.2.6
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 4.2.6
111
+ description: This is a native Ruby port of David Malmborg's ByteBoozer 2.0.
112
+ email:
113
+ - djgruby@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rubocop.yml"
120
+ - Gemfile
121
+ - Gemfile.lock
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - bin/console
126
+ - bin/setup
127
+ - byteboozer2.gemspec
128
+ - lib/byteboozer2.rb
129
+ - lib/byteboozer2/cruncher.rb
130
+ - lib/byteboozer2/file.rb
131
+ - lib/byteboozer2/version.rb
132
+ - src/decruncher.inc
133
+ homepage: https://github.com/pawelkrol/byteboozer2_ruby
134
+ licenses:
135
+ - MIT
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubyforge_project:
153
+ rubygems_version: 2.4.8
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: A data cruncher for Commodore files written in pure Ruby
157
+ test_files: []