sabrina 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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