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.
- checksums.yaml +7 -0
- data/LICENSE.txt +674 -0
- data/README.md +66 -0
- data/example/README.md +3 -0
- data/example/disasm.rb +34 -0
- data/exe/ncpp +4 -0
- data/lib/ncpp/commands.rb +903 -0
- data/lib/ncpp/interpreter.rb +919 -0
- data/lib/ncpp/parser.rb +249 -0
- data/lib/ncpp/types.rb +68 -0
- data/lib/ncpp/utils.rb +700 -0
- data/lib/ncpp/version.rb +4 -0
- data/lib/ncpp.rb +478 -0
- data/lib/nitro/nitro.dll +0 -0
- data/lib/nitro/nitro.rb +440 -0
- data/lib/unarm/unarm.dll +0 -0
- data/lib/unarm/unarm.rb +836 -0
- metadata +91 -0
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
|
data/lib/ncpp/version.rb
ADDED