byteinterpreter 1.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b0eb3381f96d234f733f66860614a9690253ea1a182560555cc438cfaff3bc31
4
+ data.tar.gz: 90025a8f011378e986a058348ddc8c638196fdc5850d83c602dc84069f53eefe
5
+ SHA512:
6
+ metadata.gz: 13d197967fb4e723bdc5500d69901fcacc234f583a4fa3dbf4cc0b6ed76b5ec44582864af580b1b96e21e017603eee0ea862c333627772d2a4c398b7d431e4cf
7
+ data.tar.gz: f2aed83bbb6a1f9316c7c59af66f49b1e588ba5b51bccbfe39d41e7f216d9691b8148b2aab662dcf1ff38b20b89be90d49a1dac46b0a81bc7b6eea47017306ae
data/LICENSE.md ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2018 Michael K Gremillion II
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,181 @@
1
+ ByteInterpreter is a tool to interpret binary data in a fixed-length data
2
+ structure into another format, or to encode data from another format into that
3
+ same fixed-length data structure.
4
+
5
+ ## Introduction
6
+ ByteInterpreter was made to assist with modifying an old Dreamcast-era RPG, by
7
+ translating some of its content (spells, abilities, etc) from binary data into
8
+ a human-readable format. Since the potential applications for this tool are more
9
+ broad than just making mods for a singular videogame, I decided to spin it off
10
+ into its own little tool for others to use.
11
+
12
+ ## Scope
13
+ ByteInterpreter isn't overly-ambitious -- it's just there to read and write
14
+ bytes for *fixed-length* data structures. That "fixed-length" bit is important
15
+ -- there are no plans for ByteInterpreter to support data structures with
16
+ variable length values. In the future I may expand ByteInterpreter's ability to
17
+ cope with variable-length data structures, but only once it works the best it
18
+ can with fixed-length structures.
19
+
20
+ Right now, it can interpret binary data in four sizes - 8-bit, 16-bit, 32-bit,
21
+ and 64-bit. It can also read strings in any arbitrary size. In the near future,
22
+ I'd like to support reading bit fields and to handle pointers appropriately.
23
+
24
+ ## Installation
25
+ ByteInterpreter can be installed with the following command -
26
+ ```
27
+ gem install byteinterpreter
28
+ ```
29
+ Afterwards, it's just a matter of requiring it in your code -
30
+ ```ruby
31
+ require 'byteinterpreter'
32
+ ```
33
+
34
+ ## Examples
35
+ In this example, we're trying to mod an old RPG named *Last Illusion VII*. We
36
+ have the binary file that contains the game's spells, and we know the format
37
+ for those spells is something like:
38
+ 1. Unsigned 8-bit integer - Spell element
39
+ 2. String, 20 characters - Spell name
40
+ 3. Unsigned 16-bit integer - Spell damage
41
+ 4. String, 50 characters - Spell description
42
+ 5. Signed 8-bit integer - Spell speed
43
+
44
+ So how do we extract the information from this binary file?
45
+
46
+ ### Reading bit by bit
47
+ The simplest way is to make a new instance of ByteInterpreter, load the binary
48
+ file, and read the information bit by bit with `#interpret_bytes` and
49
+ `#interpret_string`.
50
+ ```ruby
51
+ require 'byteinterpreter'
52
+
53
+ f = File.open("SPELLS.BIN", "rb")
54
+ bi = ByteInterpreter.new(stream: f)
55
+
56
+ # Interpret the first spell's element
57
+ element = bi.interpret_bytes(size: 1, signed: false)
58
+ # => 1
59
+
60
+ # Interpret the first spell's name
61
+ name = bi.interpret_string(size: 20)
62
+ # => "Fireball "
63
+ ```
64
+ These two methods can read most simple structures in binary files. Take care
65
+ to read values as `signed` when appropriate! For instance, in our example format,
66
+ spell speed is signed, so we'd read it thus:
67
+ ```ruby
68
+ bi.interpret_bytes(size: 1, signed: true)
69
+ # => -50
70
+ ```
71
+ Technically speaking, nothing truly horrid will happen if you forget, but your
72
+ data will be inaccurate.
73
+
74
+ The other method of reading bytes from a binary file is to use Instructions.
75
+
76
+ ### Using Instructions
77
+ In order to use Instructions, you must first load them. As of writing this
78
+ README, ByteInterpreter only knows how to load Instructions from JSON. You can
79
+ load JSON instructions with the `#load_instructions` method:
80
+ ```ruby
81
+ bi.load_instructions(type: :json, filename: "spell_instructions.json")
82
+ ```
83
+ The expected format of a JSON file for Instructions is an array containing
84
+ objects, one object for each field we'll be reading. The objects should each
85
+ have the following attributes: `"key"`, `"type"`, `"size"`, and `"signed"`.
86
+ * `key` - The name of the field we're reading (e.g. "Element" or "Name")
87
+ * `type` - The type of the field, either "bin" for binary values or "str" for
88
+ strings.
89
+ * `size` - How many bytes the field is. Binary values can only be 1, 2, 4, or
90
+ 8 bytes, but string values can be as short or long as you wish.
91
+ * `signed` - For binary values, if the resulting integer is signed or not.
92
+ Totally ignored for strings.
93
+
94
+ So a JSON Instructions file for our example might look like this:
95
+ ```json
96
+ [
97
+ {
98
+ "key": "element", "type": "bin", "size": 1, "signed": false
99
+ },
100
+ {
101
+ "key": "name", "type": "str", "size": 20, "signed": false
102
+ },
103
+ {
104
+ "key": "power", "type": "bin", "size": 2, "signed": false
105
+ },
106
+ {
107
+ "key": "description", "type": "str", "size": 50, "signed": false
108
+ },
109
+ {
110
+ "key": "speed", "type": "bin", "size": 1, "signed": true
111
+ }
112
+ ]
113
+ ```
114
+ If your Instructions are incorrectly formatted, never fear: ByteInterpreter
115
+ will raise a `ValidationError` and let you know what you did wrong. Also note:
116
+ it's very important that the objects in the array are in the same order as the
117
+ fields in the structure you're reading, as it will read the binary file in the
118
+ exact same order as specified in the JSON array. The validation methods won't
119
+ catch this, as ByteInterpreter has no idea what format your file is supposed to
120
+ be in.
121
+
122
+ Once you load these instructions, using them is very easy. We use the
123
+ `#interpret_from_instructions` method, which takes a block and passes the
124
+ key and interpreted value of each individual instruction to the given block.
125
+ ```ruby
126
+ spell = {}
127
+ bi.load_instructions(type: :json, filename: "spell_instructions.json")
128
+ bi.interpret_from_instructions do |key, value|
129
+ spell[key] = value
130
+ end
131
+
132
+ spell.inspect
133
+ # => {:element => 1, :name => "Fireball ", etc.
134
+ ```
135
+ _**Note:** You may have noticed in the examples, but ByteInterpreter will
136
+ preserve any whitespace given in strings. It makes no assumptions about what
137
+ you want to do with the interpreted string, so if you want to remove any
138
+ whitespace you will have to call `#chomp` yourself._
139
+
140
+ Reading the entire spell file would just involve wrapping your call to
141
+ `#interpret_from_instructions` inside a loop of some sort:
142
+ ```ruby
143
+ spellbook = Array.new
144
+ 30.times do
145
+ spell = {}
146
+ bi.interpret_from_instructions do |key, value|
147
+ spell[key] = value
148
+ end
149
+ spellbook.push(spell)
150
+ end
151
+ ```
152
+
153
+ ### Writing bytes
154
+ Writing bytes is almost as easy as reading them.
155
+ ```ruby
156
+ f = File.new("NEW_SPELLS.BIN", "wb")
157
+ bi = ByteInterpreter.new(stream: f)
158
+
159
+ bi.encode_bytes(value: 1, size: 1, signed: false)
160
+ bi.encode_string(value: "Fireball", size: 20)
161
+ ```
162
+ These methods work about how you expect them to. `#encode_bytes` makes no
163
+ attempt to ensure the value you're passing to it actually fits into the given
164
+ size in bytes, so you should probably do some checking before encoding.
165
+ Thankfully, `#encode_strings` is friendlier and will either pad or truncate
166
+ your string to fit the appropriate size.
167
+
168
+ And of course, encoding works with instructions as well.
169
+ ```ruby
170
+ new_spell = {element: 1, name: "Fireball", etc.}
171
+ bi.encode_from_instructions(values: new_spell)
172
+ ```
173
+ `#encode_from_instructions` takes only one argument, a `Hash` with keys
174
+ matching each field key in the Instructions file, and values paired with those
175
+ keys that match the type for that field in the Instructions file. It is
176
+ **very** important that your given `Hash` contains one key for each field;
177
+ otherwise `#encode_from_instructions` will attempt to read a nonexistant key.
178
+
179
+ If you'd like to write your own Instructions, see the
180
+ [instructions.rb file](./lib/byte_interpreter/instructions.rb), it has a fairly
181
+ in-depth explanation on doing so.
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ class ByteInterpreter
6
+ ##
7
+ # The Instructions class represents a collection of ordered operations to
8
+ # perform on an IO stream. This class is used by ByteInterpreter to either
9
+ # interpret or encode bytes in a rigid, structured way.
10
+ #
11
+ # At the most basic level, Instructions are just an Array filled with Hashes,
12
+ # each Hash having exactly four keys -- :key, :type, :size, and :signed. Each
13
+ # key has requirements for its value:
14
+ # - :key -- Must be a value easily convertible to a Symbol object.
15
+ # - :type -- Must match one of the elements in the constant VALID_TYPES.
16
+ # - :size -- For binary types ("bin"), must match one of the elements in the
17
+ # constant VALID_BIN_SIZES. String types ("str") must be a
18
+ # positive Integer.
19
+ # - :signed -- For binary types ("bin"), must be the +true+ or +false+
20
+ # literals. String types ("str") ignore this value completely.
21
+ #
22
+ # Writing your own method for loading instructions is fairly simple. The
23
+ # method must call #add_field in the desired order of instruction execution,
24
+ # passing to it a Hash that conforms to the requirements above. The method
25
+ # must also be named +load_from_type+, where +type+ is what will be
26
+ # passed into ByteInterpreter#load_instructions. See #load_from_json for an
27
+ # example of this.
28
+ class Instructions
29
+ ##
30
+ # Raised by instruction validation methods.
31
+ class ValidationError < StandardError
32
+ end
33
+
34
+ ##
35
+ # Valid values for the :type key in the instructions Hash.
36
+ VALID_TYPES = %w[bin str].freeze
37
+
38
+ ##
39
+ # Valid values for binary types for the :size key in the instructions Hash.
40
+ VALID_BIN_SIZES = [1, 2, 4, 8].freeze
41
+
42
+ ##
43
+ # Keys that are in every properly-formatted instructions Hash.
44
+ FIELD_NAMES = %i[key type size signed].freeze
45
+
46
+ ##
47
+ # Creates a blank Instructions object.
48
+ def initialize
49
+ @data = []
50
+ end
51
+
52
+ ##
53
+ # Passes the given block to the internal Array's #each method.
54
+ def each(&block)
55
+ @data.each(&block)
56
+ end
57
+
58
+ ##
59
+ # Clears all loaded instructions.
60
+ def clear
61
+ @data.clear
62
+ end
63
+
64
+ ##
65
+ # Adds the given Hash to the end of the list of instructions, and validates
66
+ # it.
67
+ # @param field [Hash] A properly-formatted instructions Hash. See the
68
+ # documentation for this class on the appropriate format for this Hash.
69
+ # @return [void]
70
+ def add_field(field:)
71
+ @data.push(field.select { |k, _v| FIELD_NAMES.include? k })
72
+ validate_field(field: @data.last)
73
+ end
74
+
75
+ ##
76
+ # Loads instructions from a JSON file. The JSON file should contain a
77
+ # top-level array, with each element being an object with the appropriate
78
+ # keys and values. Keys are automatically converted from strings to
79
+ # symbols, but boolean values are not converted from strings to literals.
80
+ # @param filename [String] The filename of the JSON file to load,
81
+ # **including** the filetype extension, if any.
82
+ # @return [void]
83
+ def load_from_json(filename:)
84
+ json_fields = JSON.parse(File.open(filename, "rt", &:read), symbolize_names: true)
85
+ json_fields.each do |field|
86
+ add_field(field: field)
87
+ end
88
+ end
89
+
90
+ ##
91
+ # Validates a given Hash to ensure it conforms to the instruction format.
92
+ # @param field [Hash] The Hash object to evaluate.
93
+ # @return [Boolean]
94
+ # @raise [ValidationError] if the Hash does not conform to the instruction
95
+ # format
96
+ def validate_field(field:)
97
+ unless VALID_TYPES.include? field[:type]
98
+ raise ValidationError, "Illegal type defined at key \"#{field[:key]}\": #{field[:type]}.
99
+ Valid types are #{VALID_TYPES}."
100
+ end
101
+
102
+ if (field[:type] == "bin") && !VALID_BIN_SIZES.include?(field[:size])
103
+ raise ValidationError, "Illegal size defined for binary field at key \"#{field[:key]}\": #{field[:size]}.
104
+ Valid sizes for binary values are #{VALID_BIN_SIZES}."
105
+ end
106
+
107
+ true
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "byteinterpreter/instructions.rb"
4
+
5
+ ##
6
+ # The ByteInterpreter is a tool used to extract bytes and strings from a binary
7
+ # file, and also to encode bytes and strings into a binary file. It can also
8
+ # take a series of instructions to extract or encode data in an ordinal manner,
9
+ # suitable for writing binary files with rigid structure size requirements.
10
+ class ByteInterpreter
11
+ ##
12
+ # Reads the endian mode being used by the interpreter.
13
+ attr_reader :endian_mode
14
+
15
+ ##
16
+ # Creates and sets up a new ByteInterpreter.
17
+ # @param endian [:little, :big, nil] Default for this value is nil. The
18
+ # endian mode that will be used by the interpreter for reading/writing
19
+ # bytes. If nil is specified, the interpreter will assume machine-native
20
+ # endianness.
21
+ # @param stream [#read, #write] The IO stream, or IO-like object, that the
22
+ # interpreter will perform operations on. The interpreter will not open or
23
+ # close the stream for you, and assumes you have already changed the
24
+ # position to the appropriate offset for its operations. The interpreter
25
+ # also assumes you have opened the stream as binary (as opposed to text),
26
+ # and for the appropriate operations (read/write).
27
+ def initialize(endian: nil, stream:)
28
+ @endian_mode = endian
29
+ @instructions = nil
30
+ @iostream = stream
31
+ end
32
+
33
+ ##
34
+ # Changes the stream being used by the interpreter for operations.
35
+ # @param new_stream [#read, #write] The IO stream, or IO-like object, that
36
+ # the interpreter will perform operations on. See #new for what is expected
37
+ # of this stream.
38
+ # @return [void]
39
+ def iostream=(new_stream)
40
+ raise ArgumentError "Object given is not stream-like." unless stream_like?(obj: new_stream)
41
+ @iostream = new_stream
42
+ end
43
+
44
+ ##
45
+ # Reads a set number of bytes, interprets them into an integer, and returns
46
+ # the result.
47
+ # @param size [1, 2, 4, 8] The number of bytes to read from the stream.
48
+ # ByteInterpreter can only interpret 8-, 16-, 32-, and 64-bit values at
49
+ # this time, so this parameter is limited to just a few numbers.
50
+ # @param signed [Boolean] Default for this value is false. Set this to
51
+ # true if the bytes being read can be negative or positive.
52
+ # @return [Integer] the interpreted byte
53
+ def interpret_bytes(size: 2, signed: false)
54
+ bytes = @iostream.read(size)
55
+ directive = build_directive(size: size, signed: signed)
56
+
57
+ bytes.unpack(directive).first
58
+ end
59
+
60
+ ##
61
+ # Reads a set number of bytes, interprets them into a string, and returns the
62
+ # result.
63
+ # @param size [Integer] The number of bytes to read from the stream. Unlike
64
+ # #interpret_bytes, this size can be any positive integer.
65
+ # @return [String] the interpreted string
66
+ def interpret_string(size:)
67
+ @iostream.read(size)
68
+ end
69
+
70
+ ##
71
+ # Writes a set number of bytes, encoded from the given value.
72
+ # @param value [Integer] The value to encode and write.
73
+ # @param size [1,2,4,8] The size of the value in bytes.
74
+ # @param signed [Boolean] Set this to true if the bytes being written can be
75
+ # negative and positive, false otherwise.
76
+ # @return [void]
77
+ # @note The interpreter makes no attempt to ensure that your +value+ fits
78
+ # into +size+ bytes. To avoid unintended behavior, you should validate your
79
+ # input into this method.
80
+ def encode_bytes(value:, size:, signed:)
81
+ value = Array(value) unless value.respond_to? :pack
82
+ @iostream.write(value.pack(build_directive(size: size, signed: signed)))
83
+ end
84
+
85
+ ##
86
+ # Writes a string into a given number of bytes.
87
+ # @param value [String] The value to write to the stream.
88
+ # @param size [Integer] The size of the value in bytes. Unlike
89
+ # #encode_bytes, this size can be any positive integer.
90
+ # @return [void]
91
+ # @note If +value+ is smaller than +size+, the interpreter will pad +value+
92
+ # with 0x20 to fill the remaining space. Even so, care should be taken to
93
+ # validate your input to this method, especially if you want to handle
94
+ # strings that are larger than +size+, or want to handle size differences
95
+ # differently than this method.
96
+ def encode_string(value:, size:)
97
+ @iostream.write(value.slice(0, size).ljust(size, "\x20"))
98
+ end
99
+
100
+ ##
101
+ # Loads instructions from a file for structured, ordinal operations.
102
+ # @param type [Symbol] The type of the file that holds the instructions.
103
+ # This argument **must** have a corresponding method in the
104
+ # ByteInterpreter::Instructions class, named +load_from_type+, replacing
105
+ # +type+ with the actual name of the filetype.
106
+ # @param filename [String] The filename of the instructions to load.
107
+ # @return [void]
108
+ # @note ByteInterpreter comes with only one +type+ built-in: JSON.
109
+ def load_instructions(type:, filename:)
110
+ @instructions = Instructions.new if @instructions.nil?
111
+ @instructions.clear
112
+ @instructions.send("load_from_" + type.to_s, filename: filename)
113
+ end
114
+
115
+ ##
116
+ # Uses the loaded instructions (you did call #load_instructions first,
117
+ # right?) to interpret bytes and strings from the stream, passing them as
118
+ # arguments to the given block.
119
+ # @yieldparam key [Symbol] The key of the interpreted data. Typically used to
120
+ # set variables in the calling object.
121
+ # @yieldparam value [Integer, String] The value of the interpreted data.
122
+ # @return [Integer] the combined size, in bytes, of the operation
123
+ def interpret_from_instructions
124
+ struct_size = 0
125
+ @instructions.each do |field|
126
+ if field[:type] == "bin"
127
+ value = interpret_bytes(size: field[:size], signed: field[:signed])
128
+ elsif field[:type] == "str"
129
+ value = interpret_string(size: field[:size])
130
+ end
131
+
132
+ struct_size += field[:size]
133
+
134
+ yield field[:key], value
135
+ end
136
+
137
+ struct_size
138
+ end
139
+
140
+ ##
141
+ # Uses the loaded instructions (you did call #load_instructions first,
142
+ # right?) to encode the given values into bytes and strings, and write them
143
+ # to the stream.
144
+ #
145
+ # This method encodes and writes bytes in the order of the loaded
146
+ # instructions; this means it will seek each key from the given Hash, instead
147
+ # of seeking around the file and writing in whatever order the Hash may be
148
+ # in.
149
+ # @param values [Hash] The values to read and encode. This Hash **must**
150
+ # have keys that match *all* keys from the loaded instructions.
151
+ # @return [Integer] the combined size, in bytes, of the operation
152
+ def encode_from_instructions(values:)
153
+ struct_size = 0
154
+ @instructions.each do |field|
155
+ key = field[:key].to_sym
156
+
157
+ if field[:type] == "bin"
158
+ encode_bytes(value: values[key], size: field[:size], signed: field[:signed])
159
+ elsif field[:type] == "str"
160
+ encode_string(value: values[key], size: field[:size])
161
+ end
162
+
163
+ struct_size += field[:size]
164
+ end
165
+
166
+ struct_size
167
+ end
168
+
169
+ private
170
+
171
+ ##
172
+ # This constant maps byte lengths to their respective Strings for the
173
+ # directives in Array#pack and String#unpack.
174
+ DIRECTIVE_SIZES = { 1 => "C", 2 => "S", 4 => "L", 8 => "Q" }.freeze
175
+
176
+ ##
177
+ # Uses DIRECTIVE_SIZES to translate a byte length to a usable String.
178
+ # @param size [1,2,4,8] The byte length to translate.
179
+ # @raise [ArgumentError] if +size+ is not 1, 2, 4, or 8.
180
+ # @return [String] the translated directive String.
181
+ def determine_directive_letter(size:)
182
+ raise ArgumentError "Invalid size argument (#{size})." unless DIRECTIVE_SIZES.key?(size)
183
+ DIRECTIVE_SIZES[size].dup
184
+ end
185
+
186
+ ##
187
+ # Returns the glyph for the set endianness, for use in building the directive
188
+ # String.
189
+ # @return [String] if endian_mode is non-nil
190
+ # @return [nil] if endian_mode is nil
191
+ def determine_endian_glyph
192
+ case endian_mode
193
+ when :little
194
+ "<"
195
+ when :big
196
+ ">"
197
+ else
198
+ ""
199
+ end
200
+ end
201
+
202
+ ##
203
+ # Builds a directive String, fit for use in Array#pack and String#unpack.
204
+ # @param size [1,2,4,8] The size to translate into a directive String.
205
+ # @param signed [Boolean] Set this to true if the bytes being written can be
206
+ # negative and positive, false otherwise.
207
+ # @return [String] the built directive String
208
+ def build_directive(size:, signed:)
209
+ directive = determine_directive_letter(size: size)
210
+ directive.downcase! if signed
211
+
212
+ directive += determine_endian_glyph if "SsLlQqJjIi".include?(directive)
213
+
214
+ directive
215
+ end
216
+
217
+ ##
218
+ # Checks if the given object is stream-like -- that is, responds to #read
219
+ # and #write.
220
+ # @param obj [Object] The object to test.
221
+ # @return [Boolean]
222
+ # @note For fun, consider making an inverse of this method named
223
+ # "illiterate?"
224
+ def stream_like?(obj:)
225
+ obj.respond_to?(:read) && obj.respond_to?(:write)
226
+ end
227
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: byteinterpreter
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael K Gremillion
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-09-13 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ The ByteInterpreter is a tool to interpret bytes from and encode bytes to
15
+ binary files. It can either do this piecemeal, or via a set of rigid,
16
+ ordered instructions that define a data structure.
17
+ email:
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE.md
23
+ - README.md
24
+ - lib/byteinterpreter.rb
25
+ - lib/byteinterpreter/instructions.rb
26
+ homepage: https://github.com/mkgremillion/ByteInterpreter
27
+ licenses:
28
+ - MIT
29
+ metadata:
30
+ source_code_uri: https://github.com/mkgremillion/ByteInterpreter
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 2.7.6
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: A tool to interpret bytes from and encode bytes to binary files.
51
+ test_files: []