sabrina 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/lib/sabrina.rb +46 -0
- data/lib/sabrina/bytestream.rb +266 -0
- data/lib/sabrina/bytestream/byte_input.rb +126 -0
- data/lib/sabrina/bytestream/byte_output.rb +112 -0
- data/lib/sabrina/bytestream/rom_operations.rb +138 -0
- data/lib/sabrina/children_manager.rb +60 -0
- data/lib/sabrina/config.rb +112 -0
- data/lib/sabrina/config/charmap_in.rb +81 -0
- data/lib/sabrina/config/charmap_out.rb +144 -0
- data/lib/sabrina/config/charmap_out_special.rb +28 -0
- data/lib/sabrina/config/main.rb +105 -0
- data/lib/sabrina/gba_string.rb +156 -0
- data/lib/sabrina/lz77.rb +161 -0
- data/lib/sabrina/meta.rb +33 -0
- data/lib/sabrina/monster.rb +147 -0
- data/lib/sabrina/palette.rb +216 -0
- data/lib/sabrina/plugin.rb +145 -0
- data/lib/sabrina/plugin/load.rb +43 -0
- data/lib/sabrina/plugin/register.rb +32 -0
- data/lib/sabrina/plugins/spritesheet.rb +196 -0
- data/lib/sabrina/plugins/stats.rb +257 -0
- data/lib/sabrina/rom.rb +302 -0
- data/lib/sabrina/sprite.rb +312 -0
- metadata +113 -0
@@ -0,0 +1,144 @@
|
|
1
|
+
Sabrina::Config.load(
|
2
|
+
charmap_out: {
|
3
|
+
'00' => ' ',
|
4
|
+
'01' => 'A',
|
5
|
+
'02' => '?',
|
6
|
+
'03' => 'A',
|
7
|
+
'04' => 'C',
|
8
|
+
'05' => 'E',
|
9
|
+
'06' => 'E',
|
10
|
+
'07' => 'E',
|
11
|
+
'08' => 'E',
|
12
|
+
'09' => '?',
|
13
|
+
'0B' => 'I',
|
14
|
+
'0C' => 'I',
|
15
|
+
'0D' => '?',
|
16
|
+
'0E' => '?',
|
17
|
+
'0F' => 'O',
|
18
|
+
'10' => 'Œ',
|
19
|
+
'11' => 'U',
|
20
|
+
'12' => '?',
|
21
|
+
'13' => 'U',
|
22
|
+
'14' => '?',
|
23
|
+
'15' => '?',
|
24
|
+
'16' => 'à',
|
25
|
+
'17' => '?',
|
26
|
+
'19' => 'ç',
|
27
|
+
'1A' => 'è',
|
28
|
+
'1B' => 'é',
|
29
|
+
'1C' => 'ê',
|
30
|
+
'1D' => 'ë',
|
31
|
+
'1E' => '?',
|
32
|
+
'20' => 'î',
|
33
|
+
'21' => 'ï',
|
34
|
+
'22' => '?',
|
35
|
+
'23' => '?',
|
36
|
+
'24' => 'ô',
|
37
|
+
'25' => 'œ',
|
38
|
+
'26' => 'ù',
|
39
|
+
'27' => '?',
|
40
|
+
'28' => 'û',
|
41
|
+
'29' => '?',
|
42
|
+
'2A' => '?',
|
43
|
+
'2B' => '?',
|
44
|
+
'2D' => '&',
|
45
|
+
'2E' => '+',
|
46
|
+
'35' => '',
|
47
|
+
'36' => ';',
|
48
|
+
'51' => '?',
|
49
|
+
'52' => '?',
|
50
|
+
'5A' => '?',
|
51
|
+
'5B' => '%',
|
52
|
+
'5C' => '(',
|
53
|
+
'5D' => ')',
|
54
|
+
'68' => 'â',
|
55
|
+
'6F' => '?',
|
56
|
+
'85' => '<',
|
57
|
+
'86' => '>',
|
58
|
+
'A1' => '0',
|
59
|
+
'A2' => '1',
|
60
|
+
'A3' => '2',
|
61
|
+
'A4' => '3',
|
62
|
+
'A5' => '4',
|
63
|
+
'A6' => '5',
|
64
|
+
'A7' => '6',
|
65
|
+
'A8' => '7',
|
66
|
+
'A9' => '8',
|
67
|
+
'AA' => '9',
|
68
|
+
'AB' => '!',
|
69
|
+
'AC' => '?',
|
70
|
+
'AD' => '.',
|
71
|
+
'AE' => '-',
|
72
|
+
'AF' => '·',
|
73
|
+
'B1' => '«',
|
74
|
+
'B2' => '»',
|
75
|
+
'B3' => '/',
|
76
|
+
'B4' => "\\",
|
77
|
+
'B5' => '♂',
|
78
|
+
'B6' => '♀',
|
79
|
+
'B7' => '$',
|
80
|
+
'B8' => ',',
|
81
|
+
'B9' => '*',
|
82
|
+
'BA' => '/',
|
83
|
+
'BB' => 'A',
|
84
|
+
'BC' => 'B',
|
85
|
+
'BD' => 'C',
|
86
|
+
'BE' => 'D',
|
87
|
+
'BF' => 'E',
|
88
|
+
'C0' => 'F',
|
89
|
+
'C1' => 'G',
|
90
|
+
'C2' => 'H',
|
91
|
+
'C3' => 'I',
|
92
|
+
'C4' => 'J',
|
93
|
+
'C5' => 'K',
|
94
|
+
'C6' => 'L',
|
95
|
+
'C7' => 'M',
|
96
|
+
'C8' => 'N',
|
97
|
+
'C9' => 'O',
|
98
|
+
'CA' => 'P',
|
99
|
+
'CB' => 'Q',
|
100
|
+
'CC' => 'R',
|
101
|
+
'CD' => 'S',
|
102
|
+
'CE' => 'T',
|
103
|
+
'CF' => 'U',
|
104
|
+
'D0' => 'V',
|
105
|
+
'D1' => 'W',
|
106
|
+
'D2' => 'X',
|
107
|
+
'D3' => 'Y',
|
108
|
+
'D4' => 'Z',
|
109
|
+
'D5' => 'a',
|
110
|
+
'D6' => 'b',
|
111
|
+
'D7' => 'c',
|
112
|
+
'D8' => 'd',
|
113
|
+
'D9' => 'e',
|
114
|
+
'DA' => 'f',
|
115
|
+
'DB' => 'g',
|
116
|
+
'DC' => 'h',
|
117
|
+
'DD' => 'i',
|
118
|
+
'DE' => 'j',
|
119
|
+
'DF' => 'k',
|
120
|
+
'E0' => 'l',
|
121
|
+
'E1' => 'm',
|
122
|
+
'E2' => 'n',
|
123
|
+
'E3' => 'o',
|
124
|
+
'E4' => 'p',
|
125
|
+
'E5' => 'q',
|
126
|
+
'E6' => 'r',
|
127
|
+
'E7' => 's',
|
128
|
+
'E8' => 't',
|
129
|
+
'E9' => 'u',
|
130
|
+
'EA' => 'v',
|
131
|
+
'EB' => 'w',
|
132
|
+
'EC' => 'x',
|
133
|
+
'ED' => 'y',
|
134
|
+
'EE' => 'z',
|
135
|
+
'F1' => '?',
|
136
|
+
'F2' => '?',
|
137
|
+
'F3' => 'U',
|
138
|
+
'F4' => '?',
|
139
|
+
'F5' => '?',
|
140
|
+
'F6' => 'ü',
|
141
|
+
'FE' => "\n",
|
142
|
+
'FF' => '$'
|
143
|
+
}
|
144
|
+
)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
Sabrina::Config.load(
|
2
|
+
charmap_out_special: {
|
3
|
+
'34' => '[Lv]',
|
4
|
+
'53' => '[pk]',
|
5
|
+
'54' => '[mn]',
|
6
|
+
'55' => '[po]',
|
7
|
+
'56' => '[ké]',
|
8
|
+
'57' => '[bl]',
|
9
|
+
'58' => '[oc]',
|
10
|
+
'59' => '[k]',
|
11
|
+
'79' => '[U]',
|
12
|
+
'7A' => '[D]',
|
13
|
+
'7B' => '[L]',
|
14
|
+
'7C' => '[R]',
|
15
|
+
'B0' => '...',
|
16
|
+
'EF' => '|>|',
|
17
|
+
'F0' => ' =>',
|
18
|
+
'F7' => '|A|',
|
19
|
+
'F8' => '|V|',
|
20
|
+
'F9' => '|<|',
|
21
|
+
'FA' => '|nb|',
|
22
|
+
'FB' => '|nb2|',
|
23
|
+
'FC' => '|FC|',
|
24
|
+
'FD' => '|FD|',
|
25
|
+
'FE' => '|br|',
|
26
|
+
'FF' => '|end|'
|
27
|
+
}
|
28
|
+
)
|
@@ -0,0 +1,105 @@
|
|
1
|
+
Sabrina::Config.load(
|
2
|
+
log_file: 'sabrina.log',
|
3
|
+
|
4
|
+
rom_defaults: {
|
5
|
+
title: 'Default ROM',
|
6
|
+
|
7
|
+
dex_blank_start: 252,
|
8
|
+
dex_blank_length: 25,
|
9
|
+
dex_length: 440,
|
10
|
+
|
11
|
+
name_length: 11,
|
12
|
+
stats_length: 28,
|
13
|
+
item_length: 44,
|
14
|
+
ability_length: 13,
|
15
|
+
type_length: 7,
|
16
|
+
|
17
|
+
frames: [1, 1],
|
18
|
+
special_frames: {
|
19
|
+
385 => [4, 4],
|
20
|
+
410 => [2, 2]
|
21
|
+
},
|
22
|
+
|
23
|
+
free_space_start: '0x740000',
|
24
|
+
|
25
|
+
name_table: '0x245EE0',
|
26
|
+
|
27
|
+
front_table: '0x2350AC',
|
28
|
+
back_table: '0x23654C',
|
29
|
+
palette_table: '0x23730C',
|
30
|
+
shinypal_table: '0x2380cc',
|
31
|
+
|
32
|
+
stats_table: '0x254784',
|
33
|
+
item_table: '0x3DB028',
|
34
|
+
ability_table: '0x24FC40',
|
35
|
+
type_table: '0x24F1A0'
|
36
|
+
},
|
37
|
+
|
38
|
+
rom_data: {
|
39
|
+
BPRE: {
|
40
|
+
title: 'FireRed (E)',
|
41
|
+
|
42
|
+
name_table: '0x245EE0',
|
43
|
+
|
44
|
+
front_table: '0x2350AC',
|
45
|
+
back_table: '0x23654C',
|
46
|
+
palette_table: '0x23730C',
|
47
|
+
shinypal_table: '0x2380cc',
|
48
|
+
|
49
|
+
stats_table: '0x254784',
|
50
|
+
item_table: '0x3DB028',
|
51
|
+
ability_table: '0x24FC40',
|
52
|
+
type_table: '0x24F1A0'
|
53
|
+
},
|
54
|
+
|
55
|
+
BPEE: {
|
56
|
+
title: 'Emerald (E)',
|
57
|
+
|
58
|
+
frames: [2, 1],
|
59
|
+
|
60
|
+
name_table: '0x3185C8',
|
61
|
+
|
62
|
+
front_table: '0x30A18C',
|
63
|
+
back_table: '0x3028B8',
|
64
|
+
palette_table: '0x303678',
|
65
|
+
shinypal_table: '0x304438',
|
66
|
+
|
67
|
+
stats_table: '0x3203CC',
|
68
|
+
item_table: '0x5839A0',
|
69
|
+
ability_table: '0x31B6DB',
|
70
|
+
type_table: '0x31AE38'
|
71
|
+
},
|
72
|
+
|
73
|
+
AXVE: {
|
74
|
+
title: 'Ruby (E)',
|
75
|
+
|
76
|
+
name_table: '0x1F716C',
|
77
|
+
|
78
|
+
front_table: '0x1E8354',
|
79
|
+
back_table: '0x1E97F4',
|
80
|
+
palette_table: '0x1EA5B4',
|
81
|
+
shinypal_table: '0x1EB374',
|
82
|
+
|
83
|
+
stats_table: '0x1FEC18',
|
84
|
+
item_table: '0x3C5564',
|
85
|
+
ability_table: '0x1FA248',
|
86
|
+
type_table: '0x1F9870'
|
87
|
+
},
|
88
|
+
|
89
|
+
MrDS: {
|
90
|
+
title: "MrDollSteak's Decap and Attack Rombase",
|
91
|
+
|
92
|
+
name_table: '0x245EE0',
|
93
|
+
|
94
|
+
front_table: '0x2350AC',
|
95
|
+
back_table: '0x23654C',
|
96
|
+
palette_table: '0x23730C',
|
97
|
+
shinypal_table: '0x2380cc',
|
98
|
+
|
99
|
+
stats_table: '0x254784',
|
100
|
+
item_table: '0x3DB028',
|
101
|
+
ability_table: '0x950000',
|
102
|
+
type_table: '0x961B50'
|
103
|
+
}
|
104
|
+
}
|
105
|
+
)
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module Sabrina
|
2
|
+
# A class for dealing with string data stored on a ROM.
|
3
|
+
#
|
4
|
+
# It is required that +:rom+ and either +:table+ and +:index+ (recommended)
|
5
|
+
# or +:offset+ be set in order to enable writing back to a ROM file.
|
6
|
+
class GBAString < Bytestream
|
7
|
+
# The byte to recognize as a blank character (space) for
|
8
|
+
# line breaking.
|
9
|
+
BLANK = "\x00"
|
10
|
+
|
11
|
+
# The byte used to right-pad short strings to the desired length.
|
12
|
+
FILLER = "\x00"
|
13
|
+
|
14
|
+
# The line break byte.
|
15
|
+
NEWLINE = "\xFE"
|
16
|
+
|
17
|
+
# The byte recognized as the end of GBA-encoded string data.
|
18
|
+
TERMINATOR = "\xFF"
|
19
|
+
|
20
|
+
# The fallback byte for GBA encoding.
|
21
|
+
MISSING_HEX = "\x00"
|
22
|
+
|
23
|
+
# The fallback character for GBA decoding.
|
24
|
+
MISSING_CHR = '?'
|
25
|
+
|
26
|
+
# An input string ending with this character will be treated as
|
27
|
+
# already terminated.
|
28
|
+
TERMINATOR_CHR = '$'
|
29
|
+
|
30
|
+
class << self
|
31
|
+
# Creates a new {GBAString} object from a ROM offset, attempting
|
32
|
+
# to read it as a GBA-encoded, 0xFF-terminated string.
|
33
|
+
#
|
34
|
+
# @param [Rom] rom
|
35
|
+
# @param [Integer, String] offset The offset to seek to in the ROM.
|
36
|
+
# See {Bytestream.parse_offset} for details.
|
37
|
+
# @param [Hash] h see {Bytestream#initialize}
|
38
|
+
# @return [GBAString]
|
39
|
+
def from_rom(rom, offset, h = {})
|
40
|
+
h.merge!(rom: rom, offset: offset)
|
41
|
+
|
42
|
+
new(h)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Same as {ByteInput#from_table}, but allows +index_length+
|
46
|
+
# to default to 11 (the typical value for a monster name table)
|
47
|
+
# and requires no +length+ due to implicit string mode.
|
48
|
+
#
|
49
|
+
# @return [GBAString]
|
50
|
+
# @see ByteInput#from_table
|
51
|
+
def from_table(rom, table, index, index_length = 11, h = {})
|
52
|
+
super(rom, table, index, index_length, nil, h)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Creates a new {GBAString} object from a string,
|
56
|
+
# encoding it to GBA format and optionally normalizing to
|
57
|
+
# +length+ and breaking within +break_range+.
|
58
|
+
#
|
59
|
+
# @param [String] s
|
60
|
+
# @param [Integer] length
|
61
|
+
# @param [Range] break_range
|
62
|
+
# @param [Hash] h see {Bytestream#initialize}
|
63
|
+
# @return [GBAString]
|
64
|
+
def from_string(s, break_range = nil, length = nil, h = {})
|
65
|
+
length ||= (s.end_with?('$') ? s.length : s.length + 1)
|
66
|
+
|
67
|
+
h.merge!(
|
68
|
+
representation: s,
|
69
|
+
length: length,
|
70
|
+
break_range: break_range
|
71
|
+
)
|
72
|
+
|
73
|
+
new(h)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Same as {Bytestream#initialize}, but with +:is_gba_string+
|
78
|
+
# set to true by default and support for the following extra
|
79
|
+
# options.
|
80
|
+
#
|
81
|
+
# @param [Hash] h
|
82
|
+
# @option h [Range] :break_range where in the string to try and
|
83
|
+
# insert a newline.
|
84
|
+
# @see Bytestream#initialize
|
85
|
+
def initialize(h = {})
|
86
|
+
@is_gba_string = true
|
87
|
+
@break_range = nil
|
88
|
+
|
89
|
+
super
|
90
|
+
end
|
91
|
+
|
92
|
+
# Attempts to decode the byte data as a GBA-encoded string.
|
93
|
+
#
|
94
|
+
# @return [String]
|
95
|
+
def present
|
96
|
+
return @representation if @representation
|
97
|
+
|
98
|
+
charmap = Config.charmap_out
|
99
|
+
# charmap.merge!(Config.charmap_out_special) if special
|
100
|
+
|
101
|
+
a = []
|
102
|
+
to_bytes.each_char do |x|
|
103
|
+
hexcode = format('%02X', x.each_byte.to_a[0])
|
104
|
+
a.push(charmap.fetch(hexcode, MISSING_CHR))
|
105
|
+
end
|
106
|
+
|
107
|
+
@representation = a.join('')
|
108
|
+
end
|
109
|
+
|
110
|
+
# Encodes the internal string data to a GBA-encoded byte stream.
|
111
|
+
#
|
112
|
+
# @return [String]
|
113
|
+
def generate_bytes
|
114
|
+
s = present.dup
|
115
|
+
|
116
|
+
a = s.scan(/./).map do |x|
|
117
|
+
hexcode = Config.charmap_in.fetch(x, MISSING_HEX)
|
118
|
+
hexcode.hex.chr
|
119
|
+
end
|
120
|
+
|
121
|
+
if @length
|
122
|
+
if a.length > @length
|
123
|
+
a.slice!(@length..-1)
|
124
|
+
else
|
125
|
+
a << FILLER until a.length >= @length
|
126
|
+
end
|
127
|
+
a[-1] = TERMINATOR
|
128
|
+
else
|
129
|
+
a << TERMINATOR
|
130
|
+
end
|
131
|
+
|
132
|
+
if @break_range
|
133
|
+
break_index = @break_range.first + (a[@break_range].rindex(BLANK) || 3)
|
134
|
+
a[break_index] = NEWLINE
|
135
|
+
end
|
136
|
+
|
137
|
+
a.map { |x| x.force_encoding('ASCII-8BIT') }
|
138
|
+
.join(''.force_encoding('ASCII-8BIT'))
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns the string representation with the end of string mark trimmed.
|
142
|
+
#
|
143
|
+
# @return [String]
|
144
|
+
def to_s
|
145
|
+
present.dup.chomp('$')
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
# @see {Bytestream#load_settings}
|
151
|
+
def load_settings(h)
|
152
|
+
@break_range = h.fetch(:break_range, @break_range)
|
153
|
+
super
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
data/lib/sabrina/lz77.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
module Sabrina
|
2
|
+
# An utility module for compressing and decompressing data in a
|
3
|
+
# GBA-compliant {http://en.wikipedia.org/wiki/LZ77_and_LZ78 LZ77} format.
|
4
|
+
#
|
5
|
+
# This has mostly been ported directly from
|
6
|
+
# {https://github.com/thekaratekid552/Secret-Tool/blob/master/lib/Tools/LZ77.py
|
7
|
+
# Gen III Hacking Suite}'s corresponding tool by thekaratekid552 and
|
8
|
+
# contributors.
|
9
|
+
#
|
10
|
+
# Credit goes to thekaratekid552, Jambo51, Shiny Quagsire,
|
11
|
+
# DoesntKnowHowToPlay, Interdpth.
|
12
|
+
module Lz77
|
13
|
+
class << self
|
14
|
+
# Decompresses data from +offset+ in the ROM file as Lz77. This returns a
|
15
|
+
# hash consisting of the uncompressed +:stream+ and the +:original_length+
|
16
|
+
# of the compressed data, but the latter value is currently inaccurate and
|
17
|
+
# should not be relied upon for wiping old data.
|
18
|
+
#
|
19
|
+
# @param [Rom] rom
|
20
|
+
# @param [Integer] offset
|
21
|
+
# @return [Hash] contains the uncompressed data as +:stream+, the
|
22
|
+
# estimated original compressed length as +:original_length+, and
|
23
|
+
# the original compressed data as +:original_stream+.
|
24
|
+
def uncompress(rom, offset)
|
25
|
+
f = rom.file
|
26
|
+
f.seek(offset)
|
27
|
+
|
28
|
+
test = f.read(1)
|
29
|
+
unless test == "\x10"
|
30
|
+
fail "Offset #{offset} in #{rom.filename} does not appear" \
|
31
|
+
" to be lz77 data. (Found #{ format('%02X', test.unpack('C')) })"
|
32
|
+
end
|
33
|
+
|
34
|
+
target_length = Bytestream.from_bytes(f.read(3).reverse).to_i
|
35
|
+
|
36
|
+
data = ''
|
37
|
+
|
38
|
+
loop do
|
39
|
+
bit_field = format('%08b', f.read(1).unpack('C').first)
|
40
|
+
|
41
|
+
bit_field.each_char do |x|
|
42
|
+
if data.length >= target_length
|
43
|
+
compressed_length = f.pos - offset
|
44
|
+
compressed_length += 1 until compressed_length % 4 == 0
|
45
|
+
original = rom.read(offset, compressed_length)
|
46
|
+
return {
|
47
|
+
stream: data.slice(0, target_length),
|
48
|
+
original_stream: original,
|
49
|
+
original_length: compressed_length
|
50
|
+
}
|
51
|
+
end
|
52
|
+
next data << f.read(1) if x == '0'
|
53
|
+
|
54
|
+
r5 = f.read(1).unpack('C').first
|
55
|
+
store = r5
|
56
|
+
r6 = 3
|
57
|
+
r3 = (r5 >> 4) + r6
|
58
|
+
r6 = store
|
59
|
+
r5 = r6 & 0xF
|
60
|
+
r12 = r5 << 8
|
61
|
+
r6 = f.read(1).unpack('C').first
|
62
|
+
r5 = r6 | r12
|
63
|
+
r12 = r5 + 1
|
64
|
+
|
65
|
+
r3.times do
|
66
|
+
unless (r5 = data[-r12])
|
67
|
+
fail "Decompression failed at offset #{offset} in" \
|
68
|
+
" #{rom.filename}. Possible corrupted file or unknown" \
|
69
|
+
" compression method. #{[bit_field, r3, r5, r6, r12]}"
|
70
|
+
end
|
71
|
+
data << r5
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Compresses the supplied stream of bytes as GBA-compliant Lz77 data.
|
78
|
+
#
|
79
|
+
# @param [String] data
|
80
|
+
# @return [String] the compressed data.
|
81
|
+
def compress(data)
|
82
|
+
compressed = "\x10"
|
83
|
+
compressed <<
|
84
|
+
Bytestream.from_hex(format('%06X', data.length)).to_b.reverse
|
85
|
+
|
86
|
+
index = 0
|
87
|
+
w = 0xFFF
|
88
|
+
window = ''
|
89
|
+
lookahead = ''
|
90
|
+
|
91
|
+
loop do
|
92
|
+
bits = ''
|
93
|
+
check = nil
|
94
|
+
current_chunk = ''
|
95
|
+
|
96
|
+
8.times do
|
97
|
+
window = (index < w ? data[0, index] : data[(index % w)..index])
|
98
|
+
lookahead = data[index..-1]
|
99
|
+
|
100
|
+
if lookahead.nil? || lookahead.empty?
|
101
|
+
unless bits.empty?
|
102
|
+
while bits.length < 8
|
103
|
+
bits << '0'
|
104
|
+
current_chunk << "\x00"
|
105
|
+
end
|
106
|
+
compressed <<
|
107
|
+
Bytestream.from_hex(format('%02x', bits.to_i(2))).to_b <<
|
108
|
+
current_chunk
|
109
|
+
end
|
110
|
+
break
|
111
|
+
end
|
112
|
+
|
113
|
+
check = window.index(lookahead[0..2])
|
114
|
+
if check
|
115
|
+
bits << '1'
|
116
|
+
length = 2
|
117
|
+
store_length = 0
|
118
|
+
store_check = 0
|
119
|
+
while check && length < 18
|
120
|
+
store_length = length
|
121
|
+
length += 1
|
122
|
+
store_check = check
|
123
|
+
check = window.index(lookahead[0, length])
|
124
|
+
end
|
125
|
+
index += store_length
|
126
|
+
store_length -= 3
|
127
|
+
position = window.length - 1 - store_check
|
128
|
+
store_length = store_length << 12
|
129
|
+
current_chunk <<
|
130
|
+
Bytestream.from_hex(format('%04X', (store_length | position))).to_b
|
131
|
+
else
|
132
|
+
index += 1
|
133
|
+
bits << '0'
|
134
|
+
current_chunk << lookahead[0]
|
135
|
+
end
|
136
|
+
end # 8.times
|
137
|
+
|
138
|
+
if lookahead.nil? || lookahead.empty?
|
139
|
+
unless bits.empty?
|
140
|
+
while bits.length < 8
|
141
|
+
bits << '0'
|
142
|
+
current_chunk << "\x00"
|
143
|
+
end
|
144
|
+
compressed <<
|
145
|
+
Bytestream.from_hex(format('%02x', bits.to_i(2))).to_b <<
|
146
|
+
current_chunk
|
147
|
+
end
|
148
|
+
break
|
149
|
+
end
|
150
|
+
|
151
|
+
compressed <<
|
152
|
+
Bytestream.from_hex(format('%02x', bits.to_i(2))).to_b <<
|
153
|
+
current_chunk
|
154
|
+
end # loop
|
155
|
+
|
156
|
+
compressed << "\x00" until compressed.length % 4 == 0
|
157
|
+
compressed
|
158
|
+
end # compress
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|