Class: Sabrina::Rom
- Inherits:
-
Object
- Object
- Sabrina::Rom
- Defined in:
- lib/sabrina/rom.rb
Overview
A class for handling low-level read and write operations upon a ROM file. Beyond creating a new Rom from filename for passing to Bytestream-family objects, you should not need to deal with it or any of its methods directly.
Constant Summary collapse
- ID_OFFSET =
The position in the ROM file from which to read the 4-byte identifier string. This is relied upon to pull the ROM type data from the Config.
172
Instance Attribute Summary collapse
-
#file ⇒ File
readonly
The ROM file object.
-
#filename ⇒ String
readonly
Just the filename of the ROM file.
-
#id ⇒ String
readonly
The 4-byte ID of the ROM.
-
#path ⇒ String
readonly
The full path and filename of the ROM file.
Class Method Summary collapse
-
.offset_to_pointer(offset) ⇒ Integer
Converts a numerical offset to a GBA-compliant, reverse 3-byte pointer.
-
.pointer_to_offset(pointer) ⇒ Integer
Converts a reverse 3-byte pointer to a numerical offset.
Instance Method Summary collapse
-
#close ⇒ 0
Closes the ROM file.
-
#find_free(length, start = nil) ⇒ Integer
Returns the position of the first occurence of
length
0xFF bytes, assumed to be free space available for writing. -
#initialize(rom_file) ⇒ Rom
constructor
Creates a new Rom object from the supplied ROM image file.
-
#monster_name(real_index) ⇒ String
Gets the name of the monster identified by
real_index
from the ROM file. -
#param(p) ⇒ Object
Fetches a specific key from the ROM Config data.
-
#read(offset, length = nil) ⇒ String
Reads
length
bytes fromoffset
, or the entire rest of the file iflength
is not specified. -
#read_lz77(offset) ⇒ Hash
Reads the data from
offset
, assuming it to be Lz77-compressed. -
#read_offset_from_table(name, index) ⇒ Integer
Reads a numerical offset associated with
index
from the tablename
. -
#read_string(offset) ⇒ String
Reads a stream expected to be an 0xFF-terminated GBA string from
offset
. -
#read_string_from_table(name, index, index_length = nil) ⇒ String
Reads a stream expected to be an 0xFF-terminated GBA string from the table.
-
#read_table(name, index, index_length = nil, length = nil) ⇒ String
Reads
length
bytes of data from the table. -
#table(name) ⇒ Integer
Returns the numerical offset associated with the
name
in the current ROM Config data. -
#table_to_offset(name, index, index_length = nil) ⇒ String
Takes a table name and index and returns a byte offset.
-
#to_s ⇒ String
Returns a blurb consisting of the ROM title and ID.
-
#wipe(offset, length, force = false) ⇒ String
Writes a stream of
length
0xFF bytes at the providedoffset
. -
#write(offset, b) ⇒ String
Writes a stream of bytes at the provided offset.
-
#write_offset_to_table(name, index, offset) ⇒ String
Writes the
offset
associated withindex
to the tablename
.
Constructor Details
#initialize(rom_file) ⇒ Rom
Creates a new Rom object from the supplied ROM image file.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/sabrina/rom.rb', line 53 def initialize(rom_file) @path = rom_file @file = File.new(rom_file, 'r+b') @filename = @path.rpartition('/').last.rpartition('.').first @id = load_id @params = Config.rom_params(@id) @params.each_key do |key| m = key.downcase.to_sym define_singleton_method(m) { @params[key] } unless respond_to?(m) end end |
Instance Attribute Details
#file ⇒ File (readonly)
The ROM file object.
31 32 33 |
# File 'lib/sabrina/rom.rb', line 31 def file @file end |
#filename ⇒ String (readonly)
Just the filename of the ROM file.
26 27 28 |
# File 'lib/sabrina/rom.rb', line 26 def filename @filename end |
#id ⇒ String (readonly)
The 4-byte ID of the ROM.
16 17 18 |
# File 'lib/sabrina/rom.rb', line 16 def id @id end |
#path ⇒ String (readonly)
The full path and filename of the ROM file.
21 22 23 |
# File 'lib/sabrina/rom.rb', line 21 def path @path end |
Class Method Details
.offset_to_pointer(offset) ⇒ Integer
Converts a numerical offset to a GBA-compliant, reverse 3-byte pointer.
38 39 40 |
# File 'lib/sabrina/rom.rb', line 38 def offset_to_pointer(offset) format('%06X', offset).scan(/../).reverse.map { |x| x.hex.chr }.join('') end |
.pointer_to_offset(pointer) ⇒ Integer
Converts a reverse 3-byte pointer to a numerical offset.
45 46 47 |
# File 'lib/sabrina/rom.rb', line 45 def pointer_to_offset(pointer) Bytestream.from_bytes(pointer.reverse).to_i end |
Instance Method Details
#close ⇒ 0
Closes the ROM file.
281 282 283 284 |
# File 'lib/sabrina/rom.rb', line 281 def close @file.close 0 end |
#find_free(length, start = nil) ⇒ Integer
Returns the position of the first occurence of length
0xFF bytes, assumed to be free space available for writing. If start
is nil
, the search will begin at the :free_space_start
offset specified in the ROM Config.
172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/sabrina/rom.rb', line 172 def find_free(length, start = nil) query = ("\xFF" * length).force_encoding('ASCII-8BIT') start ||= Bytestream.parse_offset(free_space_start) @file.seek(start) match = start + @file.read.index(query) return match if match % 4 == 0 || !match match += 1 until match % 4 == 0 find_free(length, match) end |
#monster_name(real_index) ⇒ String
Gets the name of the monster identified by real_index
from the ROM file.
92 93 94 |
# File 'lib/sabrina/rom.rb', line 92 def monster_name(real_index) read_string_from_table(:name_table, real_index, name_length) end |
#param(p) ⇒ Object
Fetches a specific key from the ROM Config data.
81 82 83 84 |
# File 'lib/sabrina/rom.rb', line 81 def param(p) s = p.to_sym @params.fetch(s) { fail "No parameter #{s} for ROM type #{@id}." } end |
#read(offset, length = nil) ⇒ String
Reads length
bytes from offset
, or the entire rest of the file if length
is not specified.
213 214 215 216 |
# File 'lib/sabrina/rom.rb', line 213 def read(offset, length = nil) @file.seek(offset) length ? @file.read(length) : @file.read end |
#read_lz77(offset) ⇒ Hash
Reads the data from offset
, assuming it to be Lz77-compressed.
191 192 193 |
# File 'lib/sabrina/rom.rb', line 191 def read_lz77(offset) Lz77.uncompress(self, offset) end |
#read_offset_from_table(name, index) ⇒ Integer
Reads a numerical offset associated with index
from the table name
. This assumes the table contains 3-byte GBA pointers.
121 122 123 124 |
# File 'lib/sabrina/rom.rb', line 121 def read_offset_from_table(name, index) pointer = read_table(name, index, 8, 3) self.class.pointer_to_offset(pointer) end |
#read_string(offset) ⇒ String
Reads a stream expected to be an 0xFF-terminated GBA string from offset
.
200 201 202 203 204 205 |
# File 'lib/sabrina/rom.rb', line 200 def read_string(offset) term = "\xFF".force_encoding('ASCII-8BIT') @file.seek(offset) @file.gets(term) end |
#read_string_from_table(name, index, index_length = nil) ⇒ String
Reads a stream expected to be an 0xFF-terminated GBA string from the table. This assumes the entries before index
are each index_length
long.
139 140 141 142 143 |
# File 'lib/sabrina/rom.rb', line 139 def read_string_from_table(name, index, index_length = nil) index_length ||= param(name.to_s.sub('_table', '_length')) s = read_string(table(name) + index * index_length) GBAString.from_bytes(s).to_s end |
#read_table(name, index, index_length = nil, length = nil) ⇒ String
Reads length
bytes of data from the table. This assumes the entries before index
are each index_length
long.
159 160 161 162 163 |
# File 'lib/sabrina/rom.rb', line 159 def read_table(name, index, index_length = nil, length = nil) index_length ||= param(name.to_s.sub('_table', '_length')) length ||= index_length read(table(name) + index * index_length, length) end |
#table(name) ⇒ Integer
Returns the numerical offset associated with the name
in the current ROM Config data.
73 74 75 |
# File 'lib/sabrina/rom.rb', line 73 def table(name) Bytestream.parse_offset(param(name)) end |
#table_to_offset(name, index, index_length = nil) ⇒ String
Takes a table name and index and returns a byte offset.
106 107 108 109 110 |
# File 'lib/sabrina/rom.rb', line 106 def table_to_offset(name, index, index_length = nil) index_length ||= param(name.to_s.sub('_table', '_length')) table(name) + index * index_length end |
#to_s ⇒ String
Returns a blurb consisting of the ROM title and ID.
289 290 291 |
# File 'lib/sabrina/rom.rb', line 289 def to_s "#{ param(:title) } [#{@id}]" end |
#wipe(offset, length, force = false) ⇒ String
Writes a stream of length
0xFF bytes at the provided offset
. This ought to be recognized as free space available for writing. Unless force
is set to true, the method will do nothing if there may be multiple pointers referencing the given offset within the ROM file.
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/sabrina/rom.rb', line 259 def wipe(offset, length, force = false) unless force pointer = self.class.offset_to_pointer(offset) @file.rewind hits = @file.read.scan(pointer).length if hits > 1 return "Rom.wipe: Offset #{offset} (#{ format('%06X', offset) })" \ " appears to be referenced by multiple pointers (#{hits})," \ ' not wiping. Use wipe(offset, length, true) to override.' end end write(offset, "\xFF" * length) "Rom#wipe: Wiped #{length} bytes at #{offset}" \ "(#{ format('%06X', offset) })." end |
#write(offset, b) ⇒ String
Writes a stream of bytes at the provided offset.
222 223 224 225 226 227 228 |
# File 'lib/sabrina/rom.rb', line 222 def write(offset, b) @file.seek(offset) @file.write(b.force_encoding('ASCII-8BIT')) "Rom#write: Wrote #{b.length} bytes at #{offset}" \ " (#{ format('%06X', offset) })." end |
#write_offset_to_table(name, index, offset) ⇒ String
Writes the offset
associated with index
to the table name
. This assumes the table contains 3-byte GBA pointers.
241 242 243 244 245 246 |
# File 'lib/sabrina/rom.rb', line 241 def write_offset_to_table(name, index, offset) write( table(name) + index * 8, self.class.offset_to_pointer(offset) ) end |