ruby-macho 0.0.2

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
+ SHA1:
3
+ metadata.gz: 686c8eeee9cd964f7c1112259c64ba6f37650262
4
+ data.tar.gz: 9c7a05e36acc9b2120263e3847ca0dbb8e8f9fd9
5
+ SHA512:
6
+ metadata.gz: ff579faa1788421e587e995b4434ff2e8ef0f5491b33c4d599577ea70fff6ac7ce3b3e4eea166d5e5e94cd5128f3050adab90b38949fdc854419fbacfa059aa2
7
+ data.tar.gz: 107c76d944b6aa7987ac1c0299f2d49ee8e48eb8a4b8a9fa046ea9fbd6821586910c0892792c14c3aefb094b02d0a2a60c8d811cddc2c3f73309933f330c6b2c
data/lib/cstruct.rb ADDED
@@ -0,0 +1,346 @@
1
+ # Struct does some trickery with custom allocators so we can't
2
+ # subclass it without writing C. Instead we define a CStruct class
3
+ # that does something similar enough for our purpose. It is
4
+ # subclassed just like any other class. A nice side-effect of this
5
+ # syntax is that it is always clear that a CStruct is just a class and
6
+ # instances of the struct are objects.
7
+ #
8
+ # Some light metaprogramming is used to make the following syntax possible:
9
+ #
10
+ # class MachHeader < CStruct
11
+ # uint :magic
12
+ # int :cputype
13
+ # int :cpusubtype
14
+ # ...
15
+ # int :flags
16
+ # end
17
+ #
18
+ # Inheritance works as you would expect.
19
+ #
20
+ # class LoadCommand < CStruct
21
+ # uint32 :cmd
22
+ # uint32 :cmdsize
23
+ # end
24
+ #
25
+ # # inherits cmd and cmdsize as the first 2 fields
26
+ # class SegmentCommand < LoadCommand
27
+ # string :segname, 16
28
+ # uint32 :vmaddr
29
+ # uint32
30
+ # end
31
+ #
32
+ # Nothing tricky or confusing there. Members of a CStruct class are
33
+ # declared in the class definition. A different definition using a
34
+ # more static approach probably wouldn't be very hard... if
35
+ # performance is critical ... but then why are you using Ruby? ;-)
36
+ #
37
+ #
38
+ # TODO support bit fields
39
+ #
40
+ # Bit fields should be supported by passing the number of bits a field
41
+ # should occupy. Perhaps we could use the size 'pack' for the rest of
42
+ # the field.
43
+ #
44
+ # class RelocationInfo < CStruct
45
+ # int32 :address
46
+ # uint32 :symbolnum, 24
47
+ # pack :pcrel, 1
48
+ # pack :length, 2
49
+ # pack :extern, 1
50
+ # pack :type, 4
51
+ # end
52
+
53
+ class CStruct
54
+
55
+
56
+ ###################
57
+ # Class Constants #
58
+ ###################
59
+
60
+ # Size in bytes.
61
+ SizeMap = {
62
+ :int8 => 1,
63
+ :uint8 => 1,
64
+ :int16 => 2,
65
+ :uint16 => 2,
66
+ :int32 => 4,
67
+ :uint32 => 4,
68
+ :int64 => 8,
69
+ :uint64 => 8,
70
+ :string => lambda { |*opts| opts.first }, # first opt is size
71
+ # the last 3 are to make the language more C-like
72
+ :int => 4,
73
+ :uint => 4,
74
+ :char => 1
75
+ }
76
+
77
+ # 32-bit
78
+ PackMap = {
79
+ :int8 => 'c',
80
+ :uint8 => 'C',
81
+ :int16 => 's',
82
+ :uint16 => 'S',
83
+ :int32 => 'i',
84
+ :uint32 => 'I',
85
+ :int64 => 'q',
86
+ :uint64 => 'Q',
87
+ :string => lambda do |str, *opts|
88
+ len = opts.first
89
+ str.ljust(len, "\0")[0, len]
90
+ end,
91
+ # a few C-like names
92
+ :int => 'i',
93
+ :uint => 'I',
94
+ :char => 'C'
95
+ }
96
+
97
+ # Only needed when unpacking is different from packing, i.e. strings w/ lambdas in PackMap.
98
+ UnpackMap = {
99
+ :string => lambda do |str, *opts|
100
+ len = opts.first
101
+ val = str[0, len-1].sub(/\0*$/, '')
102
+ str.slice!((len-1)..-1)
103
+ val
104
+ end
105
+ }
106
+
107
+ ##########################
108
+ # Class Instance Methods #
109
+ ##########################
110
+
111
+ # Note: const_get and const_set are used so the constants are bound
112
+ # at runtime, to the real class that has subclassed CStruct.
113
+ # I figured Ruby would do this but I haven't looked at the
114
+ # implementation of constants so it might be tricky.
115
+ #
116
+ # All of this could probably be avoided with Ruby 1.9 and
117
+ # private class variables. That is definitely something to
118
+ # experiment with.
119
+
120
+ class <<self
121
+
122
+ def inherited(subclass)
123
+ subclass.instance_eval do
124
+
125
+ # These "constants" are only constant references. Structs can
126
+ # be modified. After the struct is defined it is still open,
127
+ # but good practice would be not to change a struct after it
128
+ # has been defined.
129
+ #
130
+ # To support inheritance properly we try to get these
131
+ # constants from the enclosing scope (and clone them before
132
+ # modifying them!), and default to empty, er, defaults.
133
+
134
+ members = const_get(:Members).clone rescue []
135
+ member_index = const_get(:MemberIndex).clone rescue {}
136
+ member_sizes = const_get(:MemberSizes).clone rescue {}
137
+ member_opts = const_get(:MemberOptions).clone rescue {}
138
+
139
+ const_set(:Members, members)
140
+ const_set(:MemberIndex, member_index)
141
+ const_set(:MemberSizes, member_sizes)
142
+ const_set(:MemberOptions, member_opts)
143
+
144
+ end
145
+ end
146
+
147
+
148
+ # Define a method for each size name, and when that method is called it updates
149
+ # the struct class accordingly.
150
+ SizeMap.keys.each do |type|
151
+
152
+ define_method(type) do |name, *args|
153
+ name = name.to_sym
154
+ const_get(:MemberIndex)[name] = const_get(:Members).size
155
+ const_get(:MemberSizes)[name] = type
156
+ const_get(:MemberOptions)[name] = args
157
+ const_get(:Members) << name
158
+ end
159
+
160
+ end
161
+
162
+
163
+ # Return the number of members.
164
+ def size
165
+ const_get(:Members).size
166
+ end
167
+ alias_method :length, :size
168
+
169
+ # Return the number of bytes occupied in memory or on disk.
170
+ def bytesize
171
+ const_get(:Members).inject(0) { |size, name| size + sizeof(name) }
172
+ end
173
+
174
+ def sizeof(name)
175
+ value = SizeMap[const_get(:MemberSizes)[name]]
176
+ value.respond_to?(:call) ? value.call(*const_get(:MemberOptions)[name]) : value
177
+ end
178
+
179
+ def new_from_bin(bin)
180
+ new_struct = new
181
+ new_struct.unserialize(bin)
182
+ end
183
+
184
+ end
185
+
186
+
187
+ ####################
188
+ # Instance Methods #
189
+ ####################
190
+
191
+ attr_reader :values
192
+
193
+ def initialize(*args)
194
+ @values = args
195
+ end
196
+
197
+ def serialize
198
+ vals = @values.clone
199
+ membs = members.clone
200
+ pack_pattern.map do |patt|
201
+ name = membs.shift
202
+ if patt.is_a?(String)
203
+ [vals.shift].pack(patt)
204
+ else
205
+ patt.call(vals.shift, *member_options[name])
206
+ end
207
+ end.join
208
+ end
209
+
210
+ def unserialize(bin)
211
+ bin = bin.clone
212
+ @values = []
213
+ membs = members.clone
214
+ unpack_pattern.each do |patt|
215
+ name = membs.shift
216
+ if patt.is_a?(String)
217
+ @values += bin.unpack(patt)
218
+ bin.slice!(0, sizeof(name))
219
+ else
220
+ @values << patt.call(bin, *member_options[name])
221
+ end
222
+ end
223
+ self
224
+ end
225
+
226
+ def pack_pattern
227
+ members.map { |name| PackMap[member_sizes[name]] }
228
+ end
229
+
230
+ def unpack_pattern
231
+ members.map { |name| UnpackMap[member_sizes[name]] || PackMap[member_sizes[name]] }
232
+ end
233
+
234
+ def [](name_or_idx)
235
+ case name_or_idx
236
+
237
+ when Numeric
238
+ idx = name_or_idx
239
+ @values[idx]
240
+
241
+ when String, Symbol
242
+ name = name_or_idx.to_sym
243
+ @values[member_index[name]]
244
+
245
+ else
246
+ raise ArgumentError, "expected name or index, got #{name_or_idx.inspect}"
247
+ end
248
+ end
249
+
250
+ def []=(name_or_idx, value)
251
+ case name_or_idx
252
+
253
+ when Numeric
254
+ idx = name_or_idx
255
+ @values[idx] = value
256
+
257
+ when String, Symbol
258
+ name = name_or_idx.to_sym
259
+ @values[member_index[name]] = value
260
+
261
+ else
262
+ raise ArgumentError, "expected name or index, got #{name_or_idx.inspect}"
263
+ end
264
+ end
265
+
266
+ def ==(other)
267
+ puts @values.inspect
268
+ puts other.values.inspect
269
+ other.is_a?(self.class) && other.values == @values
270
+ end
271
+
272
+ # Some of these are just to quack like Ruby's built-in Struct. YAGNI, but can't hurt either.
273
+
274
+ def each(&block)
275
+ @values.each(&block)
276
+ end
277
+
278
+ def each_pair(&block)
279
+ members.zip(@values).each(&block)
280
+ end
281
+
282
+ def size
283
+ members.size
284
+ end
285
+ alias_method :length, :size
286
+
287
+ def sizeof(name)
288
+ self.class.sizeof(name)
289
+ end
290
+
291
+ def bytesize
292
+ self.class.bytesize
293
+ end
294
+
295
+ alias_method :to_a, :values
296
+
297
+
298
+ # A few convenience methods.
299
+
300
+ def members
301
+ self.class::Members
302
+ end
303
+
304
+ def member_index
305
+ self.class::MemberIndex
306
+ end
307
+
308
+ def member_sizes
309
+ self.class::MemberSizes
310
+ end
311
+
312
+ def member_options
313
+ self.class::MemberOptions
314
+ end
315
+
316
+ # The last expression is returned, so return self instead of junk.
317
+ self
318
+ end
319
+
320
+
321
+ # a small test
322
+ if $0 == __FILE__
323
+ class MachHeader < CStruct
324
+ uint :magic
325
+ int :cputype
326
+ int :cpusubtype
327
+ string :segname, 16
328
+ end
329
+ puts MachHeader::Members.inspect
330
+ puts MachHeader::MemberIndex.inspect
331
+ puts MachHeader::MemberSizes.inspect
332
+ puts "# of MachHeader members: " + MachHeader.size.to_s + ", size in bytes: " + MachHeader.bytesize.to_s
333
+ mh = MachHeader.new(0xfeedface, 7, 3, "foobar")
334
+ %w[magic cputype cpusubtype segname].each do |field|
335
+ puts "#{field}(#{MachHeader.sizeof(field.to_sym)}): #{mh[field.to_sym].inspect}"
336
+ end
337
+ puts mh.pack_pattern.inspect
338
+ binstr = mh.serialize
339
+ puts "values: " + mh.values.inspect
340
+ newmh = MachHeader.new_from_bin(binstr)
341
+ puts "new values: " + newmh.values.inspect
342
+ newbinstr = newmh.serialize
343
+ puts "serialized: " + binstr.inspect
344
+ puts "unserialized: " + newbinstr.inspect
345
+ puts "new == old ? " + (newbinstr == binstr).to_s
346
+ end
@@ -0,0 +1,17 @@
1
+ module INTHelpers
2
+ Change = Struct.new("Changes", :old, :new)
3
+
4
+ Rpath = Struct.new("Rpaths", :old, :new, :found) do
5
+ def found?
6
+ found
7
+ end
8
+ end
9
+
10
+ AddRpath = Struct.new("AddRpaths", :new)
11
+
12
+ DeleteRpath = Struct.new("DeleteRpaths", :old, :found) do
13
+ def found?
14
+ found
15
+ end
16
+ end
17
+ end
data/lib/macho.rb ADDED
@@ -0,0 +1,14 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require "cstruct"
4
+ require "macho/headers"
5
+ require "macho/structure"
6
+ require "macho/load_commands"
7
+ require "macho/sections"
8
+ require "macho/file"
9
+ require "macho/exceptions"
10
+ require "macho/utils"
11
+
12
+ module MachO
13
+ # nothing to see here.
14
+ end
@@ -0,0 +1,54 @@
1
+ module MachO
2
+ # generic toplevel error
3
+ class MachOError < RuntimeError
4
+ end
5
+
6
+ # raised when a file's magic bytes are not valid mach-o magic
7
+ class MagicError < MachOError
8
+ def initialize(num)
9
+ super "Unrecognized Mach-O magic: 0x#{"%02x" % num}"
10
+ end
11
+ end
12
+
13
+ # raised when a file's magic bytes are those of a fat binary
14
+ class FatBinaryError < MachOError
15
+ def initialize(num)
16
+ super "Unsupported fat binary (magic 0x#{"%02x" % num})"
17
+ end
18
+ end
19
+
20
+ class CPUTypeError < MachOError
21
+ def initialize(num)
22
+ super "Unrecognized CPU type: 0x#{"%02x" % num}"
23
+ end
24
+ end
25
+
26
+ class CPUSubtypeError < MachOError
27
+ def initialize(num)
28
+ super "Unrecognized CPU sub-type: 0x#{"%02x" % num}"
29
+ end
30
+ end
31
+
32
+ # raised when a mach-o file's filetype field is unknown
33
+ class FiletypeError < MachOError
34
+ def initialize(num)
35
+ super "Unrecognized Mach-O filetype code: 0x#{"%02x" % num}"
36
+ end
37
+ end
38
+
39
+ # raised when an unknown load command is encountered
40
+ class LoadCommandError < MachOError
41
+ def initialize(num)
42
+ super "Unrecognized Mach-O load command: 0x#{"%02x" % num}"
43
+ end
44
+ end
45
+
46
+ # raised when load commands are too large to fit in the current file
47
+ class HeaderPadError < MachOError
48
+ def initialize(filename)
49
+ super "Updated load commands do not fit in the header of " +
50
+ "#{filename}. #{filename} needs to be relinked, possibly with " +
51
+ "-headerpad or -headerpad_max_install_names"
52
+ end
53
+ end
54
+ end
data/lib/macho/file.rb ADDED
@@ -0,0 +1,351 @@
1
+ module MachO
2
+ class MachOFile
3
+ attr_reader :header, :load_commands
4
+
5
+ def initialize(filename)
6
+ raise ArgumentError.new("filename must be a String") unless filename.is_a? String
7
+
8
+ @filename = filename
9
+ @raw_data = open(@filename, "rb") { |f| f.read }
10
+ @header = get_mach_header
11
+ @load_commands = get_load_commands
12
+ end
13
+
14
+ def magic32?
15
+ Utils.magic32?(header[:magic])
16
+ end
17
+
18
+ def magic64?
19
+ Utils.magic64?(header[:magic])
20
+ end
21
+
22
+ # is the file executable?
23
+ def executable?
24
+ header[:filetype] == MH_EXECUTE
25
+ end
26
+
27
+ # is the file a dynamically bound shared object?
28
+ def dylib?
29
+ header[:filetype] == MH_DYLIB
30
+ end
31
+
32
+ # is the file a dynamically bound bundle?
33
+ def bundle?
34
+ header[:filetype] == MH_BUNDLE
35
+ end
36
+
37
+ def magic
38
+ header[:magic]
39
+ end
40
+
41
+ # string representation of the header's magic bytes
42
+ def magic_string
43
+ MH_MAGICS[header[:magic]]
44
+ end
45
+
46
+ # string representation of the header's filetype field
47
+ def filetype
48
+ MH_FILETYPES[header[:filetype]]
49
+ end
50
+
51
+ # string representation of the header's cputype field
52
+ def cputype
53
+ CPU_TYPES[header[:cputype]]
54
+ end
55
+
56
+ # string representation of the header's cpusubtype field
57
+ def cpusubtype
58
+ CPU_SUBTYPES[header[:cpusubtype]]
59
+ end
60
+
61
+ # number of load commands in the header
62
+ def ncmds
63
+ header[:ncmds]
64
+ end
65
+
66
+ # size of all load commands
67
+ def sizeofcmds
68
+ header[:sizeofcmds]
69
+ end
70
+
71
+ # various execution flags
72
+ def flags
73
+ header[:flags]
74
+ end
75
+
76
+ # get load commands by name
77
+ def command(name)
78
+ load_commands.select { |lc| lc.to_s == name }
79
+ end
80
+
81
+ alias :[] :command
82
+
83
+ # get all segment commands
84
+ def segments
85
+ if magic32?
86
+ command("LC_SEGMENT")
87
+ else
88
+ command("LC_SEGMENT_64")
89
+ end
90
+ end
91
+
92
+ # get the file's dylib id, if it is a dylib
93
+ def dylib_id
94
+ if !dylib?
95
+ return nil
96
+ end
97
+
98
+ dylib_id_cmd = command('LC_ID_DYLIB').first
99
+
100
+ cmdsize = dylib_id_cmd.cmdsize
101
+ offset = dylib_id_cmd.offset
102
+ stroffset = dylib_id_cmd.name
103
+
104
+ dylib_id = @raw_data.slice(offset + stroffset...offset + cmdsize).unpack("Z*").first
105
+
106
+ dylib_id
107
+ end
108
+
109
+ def dylib_id=(new_id)
110
+ if !new_id.is_a?(String)
111
+ raise ArgumentError.new("argument must be a String")
112
+ end
113
+
114
+ if !dylib?
115
+ return nil
116
+ end
117
+
118
+ if magic32?
119
+ cmd_round = 4
120
+ else
121
+ cmd_round = 8
122
+ end
123
+
124
+ new_sizeofcmds = header[:sizeofcmds]
125
+ dylib_id_cmd = command('LC_ID_DYLIB').first
126
+ old_id = dylib_id
127
+
128
+ new_pad = Utils.round(new_id.size, cmd_round) - new_id.size
129
+ old_pad = Utils.round(old_id.size, cmd_round) - old_id.size
130
+
131
+ # pad the old and new IDs with null bytes to meet command bounds
132
+ old_id << "\x00" * old_pad
133
+ new_id << "\x00" * new_pad
134
+
135
+ # calculate the new size of the DylibCommand and sizeofcmds in MH
136
+ new_size = DylibCommand.bytesize + new_id.size
137
+ new_sizeofcmds += new_size - dylib_id_cmd.cmdsize
138
+
139
+ # calculate the low file offset (offset to first section data)
140
+ low_fileoff = 2**64 # ULLONGMAX
141
+
142
+ segments.each do |seg|
143
+ sections(seg).each do |sect|
144
+ if sect.size != 0 && !sect.flag?(S_ZEROFILL) &&
145
+ !sect.flag?(S_THREAD_LOCAL_ZEROFILL) &&
146
+ sect.offset < low_fileoff
147
+
148
+ low_fileoff = sect.offset
149
+ end
150
+ end
151
+ end
152
+
153
+ if new_sizeofcmds + header.bytesize > low_fileoff
154
+ raise HeaderPadError.new(@filename)
155
+ end
156
+
157
+ # update sizeofcmds in mach_header
158
+ set_sizeofcmds(new_sizeofcmds)
159
+
160
+ # update cmdsize in the dylib_command
161
+ @raw_data[dylib_id_cmd.offset + 4, 4] = [new_size].pack("V")
162
+
163
+ # delete the old id
164
+ @raw_data.slice!(dylib_id_cmd.offset + dylib_id_cmd.name...dylib_id_cmd.offset + dylib_id_cmd.cmdsize)
165
+
166
+ # insert the new id
167
+ @raw_data.insert(dylib_id_cmd.offset + dylib_id_cmd.name, new_id)
168
+
169
+ # pad/unpad after new_sizeofcmds until offsets are corrected
170
+ null_pad = old_id.size - new_id.size
171
+
172
+ if null_pad < 0
173
+ @raw_data.slice!(new_sizeofcmds + header.bytesize, null_pad.abs)
174
+ else
175
+ @raw_data.insert(new_sizeofcmds + header.bytesize, "\x00" * null_pad)
176
+ end
177
+
178
+ # synchronize fields with the raw data
179
+ header = get_mach_header
180
+ load_commands = get_load_commands
181
+ end
182
+
183
+ # get a list of dylib paths linked to this file
184
+ def linked_dylibs
185
+ dylibs = []
186
+ dylib_cmds = command('LC_LOAD_DYLIB')
187
+
188
+ dylib_cmds.each do |dylib_cmd|
189
+ cmdsize = dylib_cmd.cmdsize
190
+ offset = dylib_cmd.offset
191
+ stroffset = dylib_cmd.name
192
+
193
+ dylib = @raw_data.slice(offset + stroffset...offset + cmdsize).unpack("Z*").first
194
+
195
+ dylibs << dylib
196
+ end
197
+
198
+ dylibs
199
+ end
200
+
201
+ # get all sections in a segment by name
202
+ def sections(segment)
203
+ sections = []
204
+
205
+ if !segment.is_a?(SegmentCommand) && !segment.is_a?(SegmentCommand64)
206
+ raise ArgumentError.new("not a valid segment")
207
+ end
208
+
209
+ if segment.nsects.zero?
210
+ return sections
211
+ end
212
+
213
+ offset = segment.offset + segment.class.bytesize
214
+
215
+ segment.nsects.times do
216
+ if segment.is_a? SegmentCommand
217
+ sections << Section.new_from_bin(@raw_data.slice(offset, Section.bytesize))
218
+ offset += Section.bytesize
219
+ else
220
+ sections << Section64.new_from_bin(@raw_data.slice(offset, Section64.bytesize))
221
+ offset += Section64.bytesize
222
+ end
223
+ end
224
+
225
+ sections
226
+ end
227
+
228
+ def write(filename)
229
+ File.open(filename, "wb") { |f| f.write(@raw_data) }
230
+ end
231
+
232
+ def write!
233
+ File.open(@filename, "wb") { |f| f.write(@raw_data) }
234
+ end
235
+
236
+ #######
237
+ private
238
+
239
+ def get_mach_header
240
+ magic = get_magic
241
+ cputype = get_cputype
242
+ cpusubtype = get_cpusubtype
243
+ filetype = get_filetype
244
+ ncmds = get_ncmds
245
+ sizeofcmds = get_sizeofcmds
246
+ flags = get_flags
247
+
248
+ if Utils.magic32?(magic)
249
+ header = MachHeader.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags)
250
+ else
251
+ # the reserved field is reserved, so just fill it with 0
252
+ header = MachHeader64.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags, 0)
253
+ end
254
+ end
255
+
256
+ def get_magic
257
+ magic = @raw_data[0..3].unpack("N").first
258
+
259
+ if !Utils.magic?(magic)
260
+ raise MagicError.new(magic)
261
+ end
262
+
263
+ # TODO: support fat (universal) binaries
264
+ if Utils.fat_magic?(magic)
265
+ raise FatBinaryError.new(magic)
266
+ end
267
+
268
+ magic
269
+ end
270
+
271
+ def get_cputype
272
+ cputype = @raw_data[4..7].unpack("V").first
273
+
274
+ if !CPU_TYPES.keys.include?(cputype)
275
+ raise CPUTypeError.new(cputype)
276
+ end
277
+
278
+ cputype
279
+ end
280
+
281
+ def get_cpusubtype
282
+ cpusubtype = @raw_data[8..11].unpack("V").first
283
+
284
+ # this mask isn't documented!
285
+ cpusubtype &= ~CPU_SUBTYPE_LIB64
286
+
287
+ if !CPU_SUBTYPES.keys.include?(cpusubtype)
288
+ raise CPUSubtypeError.new(cpusubtype)
289
+ end
290
+
291
+ cpusubtype
292
+ end
293
+
294
+ def get_filetype
295
+ filetype = @raw_data[12..15].unpack("V").first
296
+
297
+ if !MH_FILETYPES.keys.include?(filetype)
298
+ raise FiletypeError.new(filetype)
299
+ end
300
+
301
+ filetype
302
+ end
303
+
304
+ def get_ncmds
305
+ ncmds = @raw_data[16..19].unpack("V").first
306
+
307
+ ncmds
308
+ end
309
+
310
+ def get_sizeofcmds
311
+ sizeofcmds = @raw_data[20..23].unpack("V").first
312
+
313
+ sizeofcmds
314
+ end
315
+
316
+ # TODO: parse flags, maybe?
317
+ def get_flags
318
+ flags = @raw_data[24..27].unpack("V").first
319
+
320
+ flags
321
+ end
322
+
323
+ def get_load_commands
324
+ offset = header.bytesize
325
+ load_commands = []
326
+
327
+ header[:ncmds].times do
328
+ cmd = @raw_data.slice(offset, 4).unpack("V").first
329
+
330
+ if !LC_STRUCTURES.has_key?(cmd)
331
+ raise LoadCommandError.new(cmd)
332
+ end
333
+
334
+ # why do I do this? i don't like declaring constants below
335
+ # classes, and i need them to resolve...
336
+ klass = Object.const_get "MachO::#{LC_STRUCTURES[cmd]}"
337
+ command = klass.new_from_bin(offset, @raw_data.slice(offset, klass.bytesize))
338
+
339
+ load_commands << command
340
+ offset += command.cmdsize
341
+ end
342
+
343
+ load_commands
344
+ end
345
+
346
+ def set_sizeofcmds(size)
347
+ new_size = [size].pack("V")
348
+ @raw_data[20..23] = new_size
349
+ end
350
+ end
351
+ end