sabrina 0.5.5
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.
- 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
|