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
checksums.yaml
ADDED
@@ -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.
|
data/lib/sabrina.rb
ADDED
@@ -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
|