NCPrePatcher 0.2.0-x64-mingw-ucrt

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.
data/lib/ncpp/utils.rb ADDED
@@ -0,0 +1,700 @@
1
+ require_relative '../nitro/nitro.rb'
2
+ require_relative '../unarm/unarm.rb'
3
+
4
+ require 'did_you_mean/jaro_winkler'
5
+ require 'did_you_mean/levenshtein'
6
+
7
+ module NCPP
8
+ module Utils
9
+
10
+ DTYPES = [
11
+ { size: 8, signed: false, str: 'unsigned long long int' },
12
+ { size: 4, signed: false, str: 'unsigned long' },
13
+ { size: 2, signed: false, str: 'unsigned short int' },
14
+ { size: 1, signed: false, str: 'unsigned char' },
15
+ { size: 8, signed: true, str: 'signed long long int' },
16
+ { size: 4, signed: true, str: 'signed long' },
17
+ { size: 2, signed: true, str: 'signed short int' },
18
+ { size: 1, signed: true, str: 'signed char' }
19
+ ].freeze
20
+
21
+ DTYPE_IDS = {
22
+ u64: 0, u32: 1, u16: 2, u8: 3,
23
+ s64: 4, s32: 5, s16: 6, s8: 7
24
+ }.freeze
25
+
26
+ def self.valid_identifier?(name)
27
+ name.start_with?(/[A-Za-z_]/)
28
+ end
29
+
30
+ def self.valid_identifier_check(name) # checks if given name is a valid command/variable identifier
31
+ raise "Invalid identifier '#{name}'" unless valid_identifier?(name)
32
+ end
33
+
34
+ def self.check_response(obj, meth)
35
+ raise "#{obj.class} does not respond to #{meth}" unless obj.respond_to? meth
36
+ end
37
+
38
+ def self.array_check(arr, meth_name = __callee__)
39
+ raise "#{meth_name} expects an Array" unless arr.is_a? Array
40
+ end
41
+
42
+ def self.string_check(arr, meth_name = __callee__)
43
+ raise "#{meth_name} expects a String" unless arr.is_a? String
44
+ end
45
+
46
+ def self.block_check(block, meth_name = __callee__)
47
+ raise "#{meth_name} expects a Block" unless block.is_a? Block
48
+ end
49
+
50
+ def self.numeric_check(num, meth_name = __callee__)
51
+ raise "#{meth_name} expects a Numeric" unless num.is_a? Numeric
52
+ end
53
+
54
+ def self.integer_check(int, meth_name = __callee__)
55
+ raise "#{meth_name} expects an Integer" unless int.is_a? Integer
56
+ end
57
+
58
+ def self.print_warning(msg)
59
+ puts 'WARNING'.underline_yellow + ": #{msg}".yellow
60
+ end
61
+
62
+ def self.print_info(msg)
63
+ puts 'INFO'.underline_blue + ": #{msg}".blue
64
+ end
65
+
66
+ def self.addr_to_sym(addr, ov = nil)
67
+ loc = ov.nil? ? Unarm.cpu.to_s : "ov#{ov}"
68
+ syms = Unarm.get_raw_syms(loc)
69
+ sym = UnarmBind.get_sym_for_addr(addr, syms.ptr, syms.count)
70
+ raise "No symbol found for address '#{addr.to_hex}'" if sym.to_i == 0
71
+ UnarmBind::CStr.new(sym).to_s
72
+ end
73
+
74
+ def self.invalid_sym_error(sym, demangled: false)
75
+ alt = demangled ? Unarm.symbols.demangled_map.suggest_similar_key(sym) : Unarm.sym_map.suggest_similar_key(sym)
76
+ raise "'#{sym}' is not a valid symbol#{"\nDid you mean '#{alt}'?" unless alt.nil?}"
77
+ end
78
+
79
+ def self.sym_to_addr(sym)
80
+ if sym.start_with? '_Z'
81
+ addr = Unarm.sym_map[sym]
82
+ if addr.nil?
83
+ invalid_sym_error(sym)
84
+ else
85
+ addr
86
+ end
87
+ else
88
+ # pp Unarm.symbols.ambig_demangled
89
+ overloads = Unarm.symbols.ambig_demangled.filter { it[0] == sym }.map { it[1].to_hex }
90
+ if !overloads.empty?
91
+ print_warning "Demangled symbol name '#{sym}' is ambiguous.\n" \
92
+ "Overload#{'s' if overloads.length != 1 } found at: #{overloads.join(', ')}"
93
+ end
94
+ mangled = Unarm.symbols.demangled_map[sym]
95
+ invalid_sym_error(sym, demangled: true) if mangled.nil?
96
+ Unarm.sym_map[mangled]
97
+ end
98
+ end
99
+
100
+ def self.get_sym_ov(sym)
101
+ if sym.start_with?('_Z')
102
+ invalid_sym_error(sym) unless Unarm.sym_map.keys.include?(sym)
103
+ else
104
+ mangled = Unarm.symbols.demangled_map[sym]
105
+ invalid_sym_error(sym, demangled: true) if mangled.nil?
106
+ sym = mangled
107
+ end
108
+ Integer(Unarm.symbols.locs.find {|k,v| v.include?(sym)}[0][2..], exception: false)
109
+ end
110
+
111
+ def self.resolve_loc(addr, ov = nil)
112
+ if addr.is_a? String
113
+ ov = get_sym_ov(addr) if ov.nil?
114
+ addr = sym_to_addr(addr)
115
+ end
116
+ [addr, ov]
117
+ end
118
+
119
+ def self.resolve_code_loc(addr, ov)
120
+ addr, ov = resolve_loc(addr, ov)
121
+ [addr, ov, ov.nil? || ov == -1 ? $rom.arm9 : $rom.get_overlay(ov)]
122
+ end
123
+
124
+ def self.gen_hook_str(type, addr, ov = nil, arg = nil) # generates an NCPatcher hook
125
+ addr, ov = resolve_loc(addr, ov)
126
+ "ncp_#{type}(#{addr.to_hex}#{",#{ov}" if ov}#{",\"#{arg}\"" if arg})"
127
+ end
128
+
129
+ def self.gen_hook_description(type)
130
+ "Generates an NCPatcher '#{type}' hook with an address or symbol and an overlay. Specifying the overlay is " \
131
+ "optional if the address is in arm9 or a symbol is used and the symbols file consistently uses tags like " \
132
+ "these to mark symbol locations: '/* arm9_ovX */' (where X is the overlay number)."
133
+ end
134
+
135
+ def self.gen_set_hook_str(type, addr, ov = nil, arg = nil)
136
+ addr, ov = resolve_loc(addr, ov)
137
+ "ncp_#{type}(#{addr.to_hex}#{",#{ov}" if ov}#{",#{arg}" if arg})"
138
+ end
139
+
140
+ def self.gen_c_over_guard(loc, ov = nil)
141
+ addr, ov, code_bin = resolve_code_loc(loc, ov)
142
+ "ncp_over(#{addr.to_hex}#{",#{ov}" if ov})\n" \
143
+ "static const unsigned int __over_guard_#{addr.to_hex}#{"_#{ov}" if ov} = #{code_bin.read_word(addr).to_hex};"
144
+ end
145
+
146
+ def self.modify_ins_immediate(loc, ov, val, thumb: false)
147
+ addr, ov, code_bin = resolve_code_loc(loc, ov)
148
+ asm = thumb ? disasm_thumb_ins(code_bin.read_hword(addr)) : disasm_arm_ins(code_bin.read_word(addr))
149
+ raise 'No immediate found in instruction' if !asm.include? '#'
150
+ gen_hook_str('repl', addr, ov, asm[..asm.index('#')] + val.to_hex )
151
+ end
152
+
153
+ def self.gen_repl_array(loc, ov, dtype, arr, const: true)
154
+ addr, ov = resolve_loc(loc, ov)
155
+ "#{gen_hook_str('over', addr, ov)}\nstatic #{'const' if const} " \
156
+ "#{dtype.is_a?(String) ? dtype : DTYPES[dtype][:str]} __array_#{addr.to_hex}_ov#{ov}[] = #{to_c_array(arr)};"
157
+ end
158
+
159
+ def self.gen_repl_type_array(loc, ov_or_arr, dtype_sym, arr = nil)
160
+ if arr.nil?
161
+ gen_repl_array(loc, resolve_loc(loc)[1], DTYPE_IDS[dtype_sym], ov_or_arr)
162
+ else
163
+ gen_repl_array(loc, ov_or_arr, DTYPE_IDS[dtype_sym], arr)
164
+ end
165
+ end
166
+
167
+ def self.get_reloc_func(addr, ov = nil)
168
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
169
+ code_bin.reloc_function(addr)
170
+ end
171
+
172
+ def self.disasm_arm_ins(data)
173
+ raise "cannot disassemble a String" if data.is_a? String
174
+ Unarm::ArmIns.disasm(data).str
175
+ end
176
+
177
+ def self.disasm_thumb_ins(data)
178
+ raise "cannot disassemble a String" if data.is_a? String
179
+ Unarm::ThumbIns.disasm(data).str
180
+ end
181
+
182
+ def self.disasm_hex_seq(hex_byte_str, thumb: false)
183
+ if thumb
184
+ [hex_byte_str].pack('H*').unpack('S*').map { Utils.disasm_thumb_ins(it) }
185
+ else
186
+ [hex_byte_str].pack('H*').unpack('V*').map { Utils.disasm_arm_ins(it) }
187
+ end
188
+ end
189
+
190
+ def self.get_instruction(addr, ov = nil)
191
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
192
+ if addr & 1 != 0
193
+ disasm_thumb_ins(code_bin.read16(addr-1))
194
+ else
195
+ disasm_arm_ins(code_bin.read32(addr))
196
+ end
197
+ end
198
+
199
+ def self.get_raw_instruction(addr, ov = nil)
200
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
201
+ if addr & 1 != 0
202
+ code_bin.read_thumb_instruction(addr-1)
203
+ else
204
+ code_bin.read_arm_instruction(addr)
205
+ end
206
+ end
207
+
208
+ def self.get_dword(addr, ov = nil)
209
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
210
+ code_bin.read_dword(addr).unsigned(64)
211
+ end
212
+
213
+ def self.get_word(addr, ov = nil)
214
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
215
+ code_bin.read_word(addr).unsigned(32)
216
+ end
217
+
218
+ def self.get_hword(addr, ov = nil)
219
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
220
+ code_bin.read_hword(addr).unsigned(16)
221
+ end
222
+
223
+ def self.get_byte(addr, ov = nil)
224
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
225
+ code_bin.read_byte(addr).unsigned(8)
226
+ end
227
+
228
+ def self.get_signed_dword(addr, ov = nil)
229
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
230
+ code_bin.read_dword(addr).signed(64)
231
+ end
232
+
233
+ def self.get_signed_word(addr, ov = nil)
234
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
235
+ code_bin.read_word(addr).signed(32)
236
+ end
237
+
238
+ def self.get_signed_hword(addr, ov = nil)
239
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
240
+ code_bin.read_hword(addr).signed(16)
241
+ end
242
+
243
+ def self.get_signed_byte(addr, ov = nil)
244
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
245
+ code_bin.read_byte(addr).signed(8)
246
+ end
247
+
248
+ def self.get_cstring(addr, ov = nil)
249
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
250
+ code_bin.read_cstr(addr)
251
+ end
252
+
253
+ def self.get_array(addr, ov, element_type_id, element_count)
254
+ element_size = DTYPES[element_type_id][:size]
255
+ element_signed = DTYPES[element_type_id][:signed]
256
+ raise ArgumentError, 'element size must be 1, 2, 4, or 8 (bytes)' unless [1,2,4,8].include?(element_size)
257
+ addr, ov, code_bin = resolve_code_loc(addr, ov)
258
+ (0...element_count).map do |i|
259
+ offset = addr + i * element_size
260
+ if element_signed
261
+ code_bin.send(:"read#{element_size * 8}", offset).signed(element_size*8)
262
+ else
263
+ code_bin.send(:"read#{element_size * 8}", offset)
264
+ end
265
+ end
266
+ end
267
+
268
+ def self.find_branch_to(branch_dest, start_loc, start_ov=nil, from_func: false, find_all: false)
269
+ start_addr, _ov, code_bin = resolve_code_loc(start_loc, start_ov)
270
+ if branch_dest.is_a? Array
271
+ branch_dests = branch_dest.map {|dest| dest.is_a?(String) ? sym_to_addr(dest) : dest }
272
+ else
273
+ branch_dests = [branch_dest.is_a?(String) ? sym_to_addr(branch_dest) : branch_dest]
274
+ end
275
+ if from_func
276
+ func = code_bin.get_function(start_addr)
277
+ addrs = []
278
+ func[:instructions].each do |ins|
279
+ if branch_dests.include?(ins.branch_dest)
280
+ addrs << ins.addr
281
+ break unless find_all
282
+ end
283
+ end
284
+ if find_all
285
+ return addrs
286
+ else
287
+ return addrs[0] unless addrs.empty?
288
+ end
289
+ else
290
+ code_bin.each_ins(start_addr..) do |ins|
291
+ print_warning "Function end may have been passed in search for branch to " \
292
+ "#{branch_dests.map(&:to_hex).join(', ')}" if ins.function_end?
293
+ return ins.addr if branch_dests.include?(ins.branch_dest)
294
+ end
295
+ end
296
+ raise "Could not find a branch to #{branch_dests.map(&:to_hex).join(', ')}"
297
+ end
298
+
299
+ def self.track_reg(reg, from_addr,ov, to_addr)
300
+ start_addr, _ov, code_bin = resolve_code_loc(from_addr, ov)
301
+ to_addr = sym_to_addr(to_addr) if to_addr.is_a? String
302
+ reg = reg.to_sym
303
+ code_bin.each_ins(from_addr..to_addr) do |ins|
304
+ if ins.mnemonic == 'mov'
305
+ if ins.args[1].kind == :reg && ins.args[1].value.reg == reg
306
+ reg = ins.args[0].value.reg
307
+ elsif ins.args[0].value.reg == reg
308
+ reg = nil
309
+ break
310
+ end
311
+ end
312
+ end
313
+ reg = reg.to_s unless reg.nil?
314
+ end
315
+
316
+ def self.find_ins_in_func(ins_pattern_str, func_loc, func_ov = nil, find_all: false)
317
+ start_addr, _ov, code_bin = resolve_code_loc(func_loc, func_ov)
318
+ func = code_bin.get_function(start_addr)
319
+ addrs = []
320
+ func[:instructions].each do |ins|
321
+ if ins.str.match?(ins_pattern_str)
322
+ addrs << ins.addr
323
+ break unless find_all
324
+ end
325
+ end
326
+ if find_all
327
+ return addrs
328
+ else
329
+ return addrs[0] unless addrs.empty?
330
+ end
331
+ raise "Could not find instruction pattern in function at #{func_loc}"
332
+ end
333
+
334
+ def self.next_addr(current_loc, ov = nil)
335
+ addr, ov, code_bin = resolve_code_loc(current_loc,ov)
336
+ raise 'Next address is out of range' if addr >= code_bin.end_addr - 4
337
+ is_thumb = addr & 1 != 0
338
+ addr -= 1 if is_thumb
339
+ addr += is_thumb ? 2 : 4
340
+ end
341
+
342
+ def self.get_ins_mnemonic(loc, ov = nil)
343
+ addr, ov, code_bin = resolve_code_loc(loc,ov)
344
+ code_bin.read_ins(addr).mnemonic
345
+ end
346
+
347
+ def self.get_ins_arg(loc, ov, arg_index)
348
+ addr, ov, code_bin = resolve_code_loc(loc,ov)
349
+ code_bin.read_ins(addr).args[arg_index].value
350
+ end
351
+
352
+ def self.get_ins_branch_dest(loc, ov=nil)
353
+ addr, ov, code_bin = resolve_code_loc(loc,ov)
354
+ code_bin.read_ins(addr).branch_dest
355
+ end
356
+
357
+ def self.get_ins_target_addr(loc, ov=nil)
358
+ addr, ov, code_bin = resolve_code_loc(loc,ov)
359
+ code_bin.read_ins(addr).target_addr
360
+ end
361
+
362
+ def self.to_c_array(arr)
363
+ array_check(arr, 'to_c_array')
364
+ arr.to_s.gsub!('[','{').gsub!(']','}')
365
+ end
366
+
367
+ def self.get_func_literal_pool(loc, ov=nil)
368
+ addr, ov, code_bin = resolve_code_loc(loc,ov)
369
+ func = code_bin.get_function(addr)
370
+ func[:literal_pool].map {|_addr,data| data.str }
371
+ end
372
+
373
+ def self.get_func_literal_pool_values(loc, ov=nil)
374
+ addr, ov, code_bin = resolve_code_loc(loc,ov)
375
+ func = code_bin.get_function(addr)
376
+ func[:literal_pool].map {|_addr,data| data.raw }
377
+ end
378
+
379
+ def self.get_func_literal_pool_addrs(loc, ov=nil)
380
+ addr, ov, code_bin = resolve_code_loc(loc,ov)
381
+ func = code_bin.get_function(addr)
382
+ func[:literal_pool].map {|addr,_data| addr }
383
+ end
384
+
385
+ def self.get_function_size(loc, ov=nil)
386
+ addr, ov, code_bin = resolve_code_loc(loc,ov)
387
+ func = code_bin.get_function(addr)
388
+ if func[:literal_pool].empty?
389
+ last = func[:instructions].last
390
+ last.address + last.size - func[:instructions].first
391
+ else
392
+ last = func[:literal_pool][func[:literal_pool].keys.max]
393
+ last.address + last.size - func[:instructions].first.address
394
+ end
395
+
396
+ end
397
+
398
+ def self.addr_in_overlay?(addr, ov)
399
+ if ov == -1
400
+ $rom.arm9.bounds.include? addr
401
+ elsif ov == -2
402
+ $rom.arm7.bounds.include? addr
403
+ else
404
+ $rom.get_overlay(ov).bounds.include? addr
405
+ end
406
+ end
407
+
408
+ def self.addr_in_arm9?(addr)
409
+ $rom.arm9.bounds.include? addr
410
+ end
411
+
412
+ def self.addr_in_arm7?(addr)
413
+ $rom.arm7.bounds.include? addr
414
+ end
415
+
416
+ def self.find_hex_bytes(ov, hex_str)
417
+ code_bin = ov == -1 ? $rom.arm9 : (ov == -2 ? $rom.arm7 : $rom.get_overlay(ov))
418
+ code_bin.find_hex(hex_str.strip.delete(' '))
419
+ end
420
+
421
+ def self.gen_hex_edit(ov, og_hex_str, new_hex_str)
422
+ addr = find_hex_bytes(ov, og_hex_str)
423
+ gen_repl_array(addr, ov, DTYPE_IDS[:u8], [new_hex_str].pack('H*').unpack('C*'))
424
+ end
425
+
426
+ class << self
427
+ alias_method :get_u64, :get_dword
428
+ alias_method :get_u32, :get_word
429
+ alias_method :get_u16, :get_hword
430
+ alias_method :get_u8, :get_byte
431
+ alias_method :get_s64, :get_signed_dword
432
+ alias_method :get_s32, :get_signed_word
433
+ alias_method :get_s16, :get_signed_hword
434
+ alias_method :get_s8, :get_signed_byte
435
+ alias_method :get_cstr, :get_cstring
436
+ end
437
+ end
438
+ end
439
+
440
+ #
441
+ # Core class extensions
442
+ #
443
+ class Integer
444
+ def to_hex
445
+ '0x' + self.to_s(16)
446
+ end
447
+
448
+ def signed(bits)
449
+ mask = (1 << bits) - 1
450
+ n = self & mask
451
+ sign_bit = 1 << (bits - 1)
452
+ n >= sign_bit ? n - (1 << bits) : n
453
+ end
454
+
455
+ def unsigned(bits)
456
+ self & ((1 << bits) - 1)
457
+ end
458
+ end
459
+
460
+ class String
461
+ ANSI_COLORS = {
462
+ black: 30, red: 31, green: 32, yellow: 33,
463
+ blue: 34, purple: 35, cyan: 36, white: 37
464
+ }.freeze
465
+
466
+ ANSI_COLORS.each do |color, val|
467
+ define_method(color) do
468
+ "\e[#{val}m#{self}\e[0m"
469
+ end
470
+ end
471
+
472
+ ANSI_COLORS.each do |color, val|
473
+ define_method("bold_#{color.to_s}".to_sym) do
474
+ "\e[1;#{val}m#{self}\e[0m"
475
+ end
476
+ end
477
+
478
+ ANSI_COLORS.each do |color, val|
479
+ define_method("underline_#{color.to_s}".to_sym) do
480
+ "\e[4;#{val}m#{self}\e[0m"
481
+ end
482
+ end
483
+
484
+ ANSI_COLORS.each do |color, val|
485
+ define_method("bg_#{color.to_s}".to_sym) do
486
+ "\e[#{val+10}m#{self}\e[0m"
487
+ end
488
+ end
489
+ end
490
+
491
+ class Proc
492
+ def returns(type)
493
+ define_singleton_method(:return_type) { type }
494
+ self
495
+ end
496
+ def return_type
497
+ nil
498
+ end
499
+
500
+ def ignore_unk_var_at_arg(*arg_idx)
501
+ define_singleton_method(:ignore_unk_var_args) { [*arg_idx] }
502
+ self
503
+ end
504
+ def ignore_unk_var_args
505
+ []
506
+ end
507
+
508
+ def impure
509
+ define_singleton_method(:pure?) { false }
510
+ self
511
+ end
512
+ def pure?
513
+ true # NCPP commands that are Procs are marked as pure by default
514
+ end
515
+
516
+ def describe(desc)
517
+ define_singleton_method(:description) { desc }
518
+ self
519
+ end
520
+ def description
521
+ nil
522
+ end
523
+ end
524
+
525
+ class Hash
526
+ KEY_SUGGEST_THRESH = 0.7 # if more than 70% certain, suggest key
527
+
528
+ KEY_SUGGEST_JARO_WEIGHT = 0.7 # 70% Jaro-Winkler
529
+ KEY_SUGGEST_LEVENSHTEIN_WEIGHT = 0.3 # 30% Levenshtein
530
+
531
+ def suggest_similar_key(key_name)
532
+ key_name = key_name.to_s.downcase
533
+ scores = self.map do |k, _|
534
+ jw = DidYouMean::JaroWinkler.distance(key_name, k.to_s)
535
+ lev = 1.0 - [DidYouMean::Levenshtein.distance(key_name, k.to_s) / [key_name.length].max.to_f, 1.0].min
536
+ jw * KEY_SUGGEST_JARO_WEIGHT + lev * KEY_SUGGEST_LEVENSHTEIN_WEIGHT
537
+ end
538
+ max_score = scores.max
539
+ if max_score < KEY_SUGGEST_THRESH
540
+ nil
541
+ else
542
+ self.keys[scores.index(max_score)].to_s
543
+ end
544
+ end
545
+ end
546
+
547
+ #
548
+ # Various utility methods tying Nitro to the Unarm module
549
+ #
550
+ class Nitro::CodeBin
551
+ attr_reader :functions
552
+
553
+ def get_location
554
+ respond_to?(:id) ? "ov#{id}" : Unarm.cpu.to_s
555
+ end
556
+ alias_method :get_loc, :get_location
557
+
558
+ def read_arm_instruction(addr)
559
+ Unarm::ArmIns.disasm(read32(addr), addr, get_loc)
560
+ end
561
+ alias_method :read_arm_ins, :read_arm_instruction
562
+ alias_method :read_ins, :read_arm_instruction
563
+
564
+ def read_thumb_instruction(addr)
565
+ Unarm::ThumbIns.disasm(read16(addr), addr, get_loc)
566
+ end
567
+ alias_method :read_thumb_ins, :read_thumb_instruction
568
+
569
+ def each_arm_instruction(range = bounds)
570
+ each_word(range) do |word, addr|
571
+ yield Unarm::ArmIns.disasm(word, addr, get_loc)
572
+ end
573
+ end
574
+ alias_method :each_arm_ins, :each_arm_instruction
575
+ alias_method :each_ins, :each_arm_instruction
576
+
577
+ def each_thumb_instruction(range = bounds)
578
+ each_hword(range) do |hword, addr|
579
+ yield Unarm::ThumbIns.disasm(hword, addr, get_loc)
580
+ end
581
+ end
582
+ alias_method :each_thumb_ins, :each_thumb_instruction
583
+
584
+ # TODO: rewrite in Rust?
585
+ def disasm_function(addr)
586
+ is_thumb = addr & 1 != 0
587
+ addr -= 1 if is_thumb
588
+
589
+ instructions = [] # [Unarm::Ins]
590
+ labels = {} # key: addr, val: [xrefs]
591
+ pool = {} # key: addr, val: Unarm::Data
592
+
593
+ send(:"each_#{is_thumb ? 'thumb' : 'arm'}_ins", addr..) do |ins|
594
+ next if pool.keys.include? ins.addr
595
+ raise "Illegal instruction found at #{ins.addr.to_hex}; this is likely not a function." if ins.illegal?
596
+
597
+ instructions << ins
598
+
599
+ if target = ins.target_addr
600
+ pool[target] ||= Unarm::Data.new(read_word(target), addr: target, loc: get_loc)
601
+ end
602
+
603
+ if ins.opcode == :b && (label_addr = ins.branch_dest)
604
+ labels[label_addr] = [] if labels[label_addr].nil?
605
+ labels[label_addr] << ins.addr # add xref
606
+
607
+ break if ins.unconditional? && ins.addr > labels.keys.max
608
+
609
+ elsif ins.function_end? && (labels.empty? || ins.addr > labels.keys.max)
610
+ break
611
+ end
612
+
613
+ if instructions.length > 2500 # TODO: is this a good threshold?
614
+ raise "Function at #{addr.to_hex} is growing exceptionally large; it is likely not a function."
615
+ end
616
+ end
617
+
618
+ if !instance_variable_defined? :@functions
619
+ instance_variable_set(:@functions, {})
620
+ end
621
+
622
+ @functions[addr] = {
623
+ thumb?: is_thumb,
624
+ instructions: instructions,
625
+ labels: labels,
626
+ literal_pool: pool,
627
+ }
628
+ end
629
+ alias_method :disasm_func, :disasm_function
630
+
631
+ # TODO: rewrite in Rust?
632
+ def get_function(addr)
633
+ func = @functions.nil? ? nil : @functions[addr]
634
+ if func.nil?
635
+ disasm_func(addr)
636
+ func = @functions[addr]
637
+ end
638
+ func
639
+ end
640
+
641
+ def reloc_function(addr)
642
+ if !instance_variable_defined? :@functions
643
+ instance_variable_set(:@functions, {})
644
+ end
645
+
646
+ func = get_function(addr)
647
+
648
+ out = ""
649
+ labels = func[:labels].keys
650
+ func[:instructions].each do |ins|
651
+ if label = labels.index(ins.addr)
652
+ out << "#{label+1}:\n"
653
+ end
654
+
655
+ if target = ins.target_addr
656
+ out << "#{ins.str[..ins.str.index(',')]} =#{(func[:literal_pool][target]).value}"
657
+
658
+ elsif ins.opcode == :b && (label = labels.index(ins.branch_dest))
659
+ out << ins.str[..ins.str.index('#')-1] << "#{label+1}f"
660
+
661
+ elsif (branch = ins.branch_dest) && ins.str.include?('#') &&
662
+ (dest = ins.str[ins.str.index('#')+1..].hex) &&
663
+ dest != branch && (dest += ins.addr) &&
664
+ (dest < func[:instructions][0].addr || dest > func[:instructions][-1].addr)
665
+
666
+ out << ins.str[..ins.str.index('#')] << dest.to_hex
667
+
668
+ else
669
+ out << ins.str
670
+ end
671
+
672
+ out << "\n"
673
+ end
674
+
675
+ out
676
+ end
677
+ alias_method :reloc_func, :reloc_function
678
+
679
+ def find_hex(hex_str)
680
+ target_bytes = [hex_str].pack('H*').unpack('C*')
681
+ target_len = target_bytes.length
682
+ found = 0
683
+ found_addr = nil
684
+
685
+ each_byte do |byte, addr|
686
+ if byte == target_bytes[found]
687
+ found += 1
688
+ found_addr = addr
689
+ break if found == target_len
690
+ else
691
+ found = 0
692
+ end
693
+ end
694
+
695
+ raise 'Could not find hex byte string in binary.' if found != target_len
696
+
697
+ found_addr - (target_len - 1) * 4
698
+ end
699
+
700
+ end
@@ -0,0 +1,4 @@
1
+
2
+ module NCPP
3
+ VERSION = '0.2.0'
4
+ end