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.
@@ -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
@@ -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