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