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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e428137b1b9c97072a5d0094df0d556abe7ced83
4
+ data.tar.gz: 558afde8551a7c6ecfea61ccbf4d33d3701d1945
5
+ SHA512:
6
+ metadata.gz: c71b152645a6f7805c3fccc0c5969e3593e70fcb9173b4d8c8923aa14820dd682501c774ae163b4d7d83f07ab471c1121120f36278080eb34b9ca7c81a06f8d3
7
+ data.tar.gz: 473823ca339a40dac88ca86a3beb6d65258c3e4c5bced4c57d36b53d6f9a9077881650f5d4c6604bd60e3f253d03e39944dc53c61f66a7faa4c22c0ef5bb5bf4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Winterbraid
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.
@@ -0,0 +1,46 @@
1
+ # {include:file:README.rdoc}
2
+ module Sabrina; end
3
+
4
+ require 'set'
5
+ require 'fileutils'
6
+ require 'json'
7
+
8
+ begin
9
+ require 'oily_png'
10
+ Sabrina.const_set(:PNG, 'oily_png')
11
+ rescue
12
+ require 'chunky_png'
13
+ Sabrina.const_set(:PNG, 'chunky_png')
14
+ end
15
+
16
+ require 'sabrina/meta.rb'
17
+
18
+ require 'sabrina/config.rb'
19
+ require 'sabrina/config/main.rb'
20
+ require 'sabrina/config/charmap_in.rb'
21
+ require 'sabrina/config/charmap_out.rb'
22
+ require 'sabrina/config/charmap_out_special.rb'
23
+
24
+ require 'sabrina/lz77.rb'
25
+ require 'sabrina/children_manager.rb'
26
+
27
+ require 'sabrina/bytestream/byte_input.rb'
28
+ require 'sabrina/bytestream/byte_output.rb'
29
+ require 'sabrina/bytestream/rom_operations.rb'
30
+ require 'sabrina/bytestream.rb'
31
+
32
+ require 'sabrina/sprite.rb'
33
+ require 'sabrina/palette.rb'
34
+ require 'sabrina/gba_string.rb'
35
+
36
+ require 'sabrina/rom.rb'
37
+ require 'sabrina/monster.rb'
38
+
39
+ require 'sabrina/plugin/register.rb'
40
+ require 'sabrina/plugin/load.rb'
41
+ require 'sabrina/plugin.rb'
42
+
43
+ require 'sabrina/plugins/spritesheet.rb'
44
+ require 'sabrina/plugins/stats.rb'
45
+
46
+ Sabrina::Config.load_user_config
@@ -0,0 +1,266 @@
1
+ module Sabrina
2
+ # A generic class for dealing with byte data related to 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
+ #
7
+ # There should be no need to use this class directly.
8
+ class Bytestream
9
+ extend ByteInput
10
+ include ByteOutput
11
+ include RomOperations
12
+
13
+ # Stores an array of debug strings related to the latest write to ROM.
14
+ #
15
+ # @return [Array]
16
+ # @see RomOperations#write_to_rom
17
+ attr_reader :last_write
18
+
19
+ # The ROM being used for operations.
20
+ #
21
+ # @return [Rom]
22
+ # @see #rom=
23
+ attr_reader :rom
24
+
25
+ # The index in the ROM table.
26
+ #
27
+ # @return [Integer]
28
+ # @see #index=
29
+ attr_reader :index
30
+
31
+ # The table code to search for data.
32
+ #
33
+ # @return [String or Symbol]
34
+ # @see #table=
35
+ attr_reader :table
36
+
37
+ # The directory for saving files. Should be created if specified but not
38
+ # existing.
39
+ #
40
+ # @return [String]
41
+ attr_accessor :work_dir
42
+
43
+ # The filename to save to, sans the extension. Subclass +to_file+ methods
44
+ # should take care of adding the right extension.
45
+ #
46
+ # @return [String]
47
+ attr_accessor :filename
48
+
49
+ class << self
50
+ # Takes an integer or a "0x"- or "x"-prefixed string and
51
+ # converts it to a valid numerical offset.
52
+ #
53
+ # @param [Integer] p_offset
54
+ # @return [Integer]
55
+ def parse_offset(p_offset)
56
+ o = p_offset.to_s.downcase
57
+
58
+ if /[^0-9a-fx]/ =~ o
59
+ fail "\'#{p_offset}\' does not look like a valid offset." \
60
+ ' Supply an integer, or a hex optionally prefixed' \
61
+ " with \'0x\' or \'x\'."
62
+ end
63
+
64
+ /[a-fx]/ =~ o ? p_offset.rpartition('x').last.hex : p_offset.to_i
65
+ end
66
+ end
67
+
68
+ # Returns a new instance of Bytestream, taking a hash of arguments
69
+ # as the option. The hash may contain the following keys, all of
70
+ # which are optional and init to +nil+ or +false+. Any invalid keys
71
+ # should be ignored.
72
+ #
73
+ # Subclasses should call this method with subclass-specific data.
74
+ # There should be no need to call this method directly.
75
+ #
76
+ # Subclasses should override the +load_settings(h)+ private method
77
+ # to allow for additional options.
78
+ #
79
+ # @param [Hash] h
80
+ # @option h [Object] :representation The internal representation
81
+ # of the byte data. Subclasses should be able to convert bytes
82
+ # to and from the representation via the {ByteOutput#present} and
83
+ # {ByteOutput#generate_bytes} methods.
84
+ # @option h [Boolean] :pointer_mode Whether to expect pointers in the
85
+ # table instead of actual data. This voids +:index_length+.
86
+ # @option h [Boolean] :lz77 Whether to read and write ROM data as
87
+ # {Lz77}-compressed.
88
+ # @option h [Boolean] :is_gba_string Whether to read ROM data as
89
+ # a GBA-encoded, 0xFF-terminated string.
90
+ # @option h [Rom] :rom A ROM to be used for reading and writing data.
91
+ # @option h [Symbol, String] :table The table to read offset data from.
92
+ # This should be the name of an option specified in the +rom_data+ hash.
93
+ # +:table+ and +:index+ will take precedence over +:offset+ if present.
94
+ # This is the recommended way of dealing with monster data,
95
+ # as it will allow automagically updating the offset upon writing
96
+ # or switching ROMs.
97
+ # @option h [Integer] :index The index to read from the table.
98
+ # See {#index=} for details.
99
+ # @option h [Integer] :index_length The length of a single table
100
+ # entry, necessary for seeking.
101
+ # @option h [Integer] :offset The position in ROM to read and write to.
102
+ # This will only be used if +:table+ or +:index+ are not present.
103
+ # See {Bytestream.parse_offset} for details.
104
+ # @option h [Integer] :length The byte length to read from the ROM.
105
+ # This will be ignored if +:lz77+ compression is enabled.
106
+ def initialize(h = {})
107
+ # Subclasses may want to override defaults, hence ||=
108
+ @work_dir ||= nil
109
+ @filename ||= nil
110
+
111
+ @representation ||= nil
112
+ @bytes_cache ||= nil
113
+ @lz77_cache ||= nil
114
+ @length_cache ||= nil
115
+
116
+ @last_write ||= nil
117
+
118
+ @old_offset ||= nil
119
+ @old_length ||= nil
120
+
121
+ @lz77 ||= false
122
+ @is_gba_string ||= false
123
+
124
+ @rom ||= nil
125
+ @table ||= nil
126
+ @index ||= nil
127
+ @offset ||= nil
128
+ @length ||= nil
129
+
130
+ @pointer_mode ||= false
131
+ @index_length ||= nil
132
+
133
+ load_settings(h)
134
+ present
135
+ end
136
+
137
+ # Returns the associated offset, first trying the table and then +:offset+.
138
+ # Returns +nil+ if neither has sufficient data.
139
+ #
140
+ # @return [Integer]
141
+ def offset
142
+ if @rom && @table && @index
143
+ return @rom.read_offset_from_table(@table, @index) if @pointer_mode
144
+ return @rom.table_to_offset(@table, @index, @index_length)
145
+ end
146
+ @offset
147
+ end
148
+
149
+ # Returns the offset as a reversed byte string.
150
+ # This is the format used internally by ROMs to reference data.
151
+ #
152
+ # @return [String]
153
+ def pointer
154
+ Rom.offset_to_pointer(offset)
155
+ end
156
+
157
+ # Sets the index under which to search for data in the +:table+.
158
+ # Compared to setting :index=>index in the options hash, this will
159
+ # assume the index is
160
+ #
161
+ # This will clear the internal cache.
162
+ #
163
+ # @param [Integer] p_index For monsters, this should be the
164
+ # real index (accounting for blank spaces between
165
+ # dex numbers 251 and 252) of the monster you want to act on.
166
+ # Ideally, a wrapper object should calculate this for you.
167
+ # @return [self]
168
+ def index=(p_index)
169
+ @index = p_index
170
+ clear_cache
171
+ @index
172
+ end
173
+
174
+ # Sets the ROM with which the byte data is to be associated.
175
+ # This is the same as setting +:rom+ in the options hash.
176
+ #
177
+ # This will clear the internal cache.
178
+ #
179
+ # @param [Rom] p_rom
180
+ # @return [Rom] new rom.
181
+ def rom=(p_rom)
182
+ @rom = p_rom
183
+ clear_cache(lz77: false)
184
+ @rom
185
+ end
186
+
187
+ # Sets the table name identifier and optionally the index.
188
+ # The table identifier should be a key in +rom_data+ config.
189
+ #
190
+ # This is the same as setting +:table+, +:index+
191
+ # in the options hash.
192
+ #
193
+ # This will clear the internal cache.
194
+ #
195
+ # @param [String, Symbol] p_table
196
+ # @param [Integer] p_index see {#index=} for details.
197
+ # @return [self]
198
+ def table=(p_table, p_index = nil)
199
+ @table = p_table
200
+ @index = p_index if p_index
201
+ clear_cache
202
+ [@table, @index]
203
+ end
204
+
205
+ # Sets the offset of the data in +:rom+.
206
+ # This is the same as setting +:offset+ in the options hash.
207
+ #
208
+ # This will clear the internal cache.
209
+ #
210
+ # @param [Integer, String] p_offset See {Bytestream.parse_offset} for details.
211
+ # @return [Integer] new offset.
212
+ def offset=(p_offset)
213
+ return self if p_offset == @offset
214
+ @old_offset = @offset
215
+ @offset = Bytestream.parse_offset(p_offset)
216
+ clear_cache
217
+ @offset
218
+ end
219
+
220
+ # Tells the object that the ROM uses {Lz77} compression
221
+ # for the data.
222
+ # This is the same as setting +:lz77+=>+true+ in the options hash.
223
+ #
224
+ # @return [self]
225
+ def lz77_mode
226
+ @lz77 = true
227
+ self
228
+ end
229
+
230
+ # Tells the object that the data is a GBA-encoded, 0xFF-terminated
231
+ # string.
232
+ # This is the same as setting :is_gba_string=>true in the options hash.
233
+ #
234
+ # @return [self]
235
+ def string_mode
236
+ @is_gba_string = true
237
+ self
238
+ end
239
+
240
+ private
241
+
242
+ # Loads a hash of options. Subclasses should override this to allow
243
+ # additional options.
244
+ #
245
+ # @param [Hash] h see {#initialize}.
246
+ # @return [self]
247
+ def load_settings(h)
248
+ @representation = h.fetch(:representation, @representation)
249
+ @bytes_cache = h.fetch(:bytes_cache, @bytes_cache)
250
+
251
+ @lz77 = h.fetch(:lz77, @lz77 || false)
252
+ @is_gba_string = h.fetch(:is_gba_string, @is_gba_string)
253
+
254
+ @rom = h.fetch(:rom, @rom)
255
+ @offset = Bytestream.parse_offset(h[:offset]) if h.key?(:offset)
256
+ @length = h.fetch(:length, @length)
257
+
258
+ @table = h.fetch(:table, @table)
259
+ @index = h.fetch(:index, @index)
260
+
261
+ @pointer_mode = h.fetch(:pointer_mode, @pointer_mode)
262
+ @index_length = h.fetch(:index_length, @index_length)
263
+ self
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,126 @@
1
+ module Sabrina
2
+ class Bytestream
3
+ # Constructors that allow {Bytestream} to create byte data
4
+ # from ROMs or other sources. All methods should accept an optional
5
+ # final hash of options.
6
+ #
7
+ # Subclasses should override or add to these methods as necessary,
8
+ # using the final hash to pass the internal representation and
9
+ # any other necessary parameters and defaults.
10
+ module ByteInput
11
+ # Creates a new {Bytestream} object from bytes.
12
+ #
13
+ # @param [String] b
14
+ # @param [Hash] h see {Bytestream#initialize}
15
+ # @return [Bytestream]
16
+ def from_bytes(b, h = {})
17
+ h.merge!(bytes_cache: b)
18
+
19
+ new(h)
20
+ end
21
+
22
+ # Creates a new {Bytestream} object that will read +length+
23
+ # bytes of data from a ROM +table+ and +index+, expecting each entry
24
+ # before the index to be +index_length+ bytes.
25
+ #
26
+ # @param [Rom] rom
27
+ # @param [String, Symbol] table a key specified in rom_data config.
28
+ # @param [Integer] index the index to read from the table.
29
+ # For monsters, it should be the real (not dex) number
30
+ # of the monster you wish to act on.
31
+ # @param [Integer] index_length the expected length of a single entry.
32
+ # @param [Integer] length how many bytes to read from the calculated
33
+ # offset.
34
+ # @param [Hash] h see {Bytestream#initialize}
35
+ # @return [Bytestream]
36
+ def from_table(rom, table, index, index_length, length = index_length, h = {})
37
+ h.merge!(
38
+ rom: rom,
39
+ table: table,
40
+ index: index,
41
+ index_length: index_length,
42
+ length: length
43
+ )
44
+
45
+ new(h)
46
+ end
47
+
48
+ # Creates a new {Bytestream} object that will read a pointer
49
+ # from a ROM table and index. This is the recommended method to
50
+ # read monster data from a ROM.
51
+ #
52
+ # @param [Rom] rom
53
+ # @param [String, Symbol] table a key specified in rom_data config.
54
+ # @param [Integer] index the index to read from the table.
55
+ # For monsters, it should be the real (not dex) number
56
+ # of the monster you wish to act on.
57
+ # @param [Hash] h see {Bytestream#initialize}
58
+ # @return [Bytestream]
59
+ def from_table_as_pointer(rom, table, index, h = {})
60
+ h.merge!(
61
+ rom: rom,
62
+ table: table,
63
+ index: index,
64
+ pointer_mode: true
65
+ )
66
+
67
+ new(h)
68
+ end
69
+
70
+ # Creates a new {Bytestream} object by reading +length+
71
+ # bytes from a ROM offset.
72
+ #
73
+ # @param [Rom] rom
74
+ # @param [Integer, String] offset The offset to seek to in the ROM.
75
+ # See {Bytestream.parse_offset} for details.
76
+ # @param [Integer] length
77
+ # @param [Hash] h see {Bytestream#initialize}
78
+ # @return [Bytestream]
79
+ def from_rom(rom, offset, length = nil, h = {})
80
+ h.merge!(
81
+ rom: rom,
82
+ offset: offset,
83
+ length: length
84
+ )
85
+
86
+ new(h)
87
+ end
88
+
89
+ # Creates a new {Bytestream} object from a ROM offset, attempting
90
+ # to read it as {Lz77}-compressed data.
91
+ #
92
+ # @param [Rom] rom
93
+ # @param [Integer, String] offset The offset to seek to in the ROM.
94
+ # See {Bytestream.parse_offset} for details.
95
+ # @param [Hash] h see {Bytestream#initialize}
96
+ # @return [Bytestream]
97
+ def from_rom_as_lz77(rom, offset, h = {})
98
+ h.merge!(
99
+ rom: rom,
100
+ offset: offset,
101
+ lz77: true
102
+ )
103
+
104
+ new(h)
105
+ end
106
+
107
+ # Same as {#from_bytes}, but takes a hexadecimal string that will be
108
+ # converted to bytes.
109
+ #
110
+ # @param [String] s
111
+ # @param [Hash] h see {Bytestream#initialize}
112
+ # @return [Bytestream]
113
+ def from_hex(s, h = {})
114
+ if /[^0-9a-fx]/ =~ s.downcase
115
+ fail "\'#{s}\' does not look like a hex string."
116
+ end
117
+
118
+ b = s.rpartition('x').last.scan(/../).map { |x| x.hex.chr }.join('')
119
+
120
+ h.merge!(bytes_cache: b)
121
+
122
+ new(h)
123
+ end
124
+ end
125
+ end
126
+ end