snes_utils 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  module SnesUtils
2
2
  Readline.completion_proc = proc do |input|
3
- Definitions::OPCODES_DATA.map { |row| row[:mnemonic] }
3
+ Wdc65816::Definitions::OPCODES_DATA.map { |row| row[:mnemonic] }
4
4
  .select { |mnemonic| mnemonic.upcase.start_with?(input.upcase) }
5
5
  end
6
6
 
@@ -13,6 +13,8 @@ module SnesUtils
13
13
  @memory = []
14
14
  end
15
15
 
16
+ @cpu = :wdc65816
17
+
16
18
  @normal_mode = true
17
19
 
18
20
  @current_addr = 0
@@ -21,6 +23,8 @@ module SnesUtils
21
23
  @index_flag = 1
22
24
 
23
25
  @next_addr_to_list = 0
26
+
27
+ @label_registry = {}
24
28
  end
25
29
 
26
30
  def run
@@ -57,52 +61,113 @@ module SnesUtils
57
61
  bytes.size
58
62
  end
59
63
 
60
- def read(filename)
64
+ def assemble_file(filename, outfile = 'out.smc')
61
65
  return 0 unless File.file?(filename)
62
66
 
63
- file = File.open(filename)
67
+ res = read(filename)
68
+ write(outfile)
69
+
70
+ return res
71
+ end
72
+
73
+ def read(filename, start_addr = nil)
74
+ return 0 unless File.file?(filename)
75
+
76
+ @label_registry = {}
64
77
 
78
+ current_addr = start_addr || @current_addr
65
79
  instructions = []
80
+ raw_bytes = []
81
+ incbin_files = []
82
+
83
+ cpu = @cpu
84
+
85
+ 2.times do |i|
86
+ @current_addr = current_addr
87
+ instructions = []
88
+ File.open(filename).each_with_index do |raw_line, line_no|
89
+ line = raw_line.split(';').first.strip.chomp
90
+ next if line.empty?
91
+
92
+ if line == '.spc700'
93
+ @cpu = :spc700
94
+ next
95
+ elsif line == '.65816'
96
+ @cpu = :wdc65816
97
+ next
98
+ end
99
+
100
+ if matches = Definitions::BYTE_SEQUENCE_REGEX.match(line)
101
+ if i == 1
102
+ addr = matches[1].to_i(16)
103
+ bytes = matches[2].delete(' ').scan(/.{1,2}/).map { |b| hex(b.to_i(16)) }
104
+ raw_bytes << [addr, bytes]
105
+ end
106
+
107
+ next
108
+ elsif matches = Definitions::INCBIN_REGEX.match(line)
109
+ if i == 1
110
+ addr = matches[1].to_i(16)
111
+ filename = matches[2].strip.chomp
66
112
 
67
- file.each_with_index do |raw_line, line_no|
68
- line = raw_line.split(';').first.strip.chomp
69
- next if line.empty?
113
+ incbin_files << [addr, filename]
114
+ end
115
+
116
+ next
117
+ end
70
118
 
71
- instruction, length, address = parse_instruction(line)
72
- return "Error at line #{line_no + 1}" unless instruction
119
+ instruction, length, address = parse_instruction(line, register_label=(i == 0), resolve_label=(i==1))
120
+ return "Error at line #{line_no + 1}" unless instruction
73
121
 
74
- instructions << [instruction, length, address]
75
- @current_addr = address + length
122
+ instructions << [instruction, length, address]
123
+ @current_addr = address + length
124
+ end
76
125
  end
77
126
 
78
- disassembled_instructions = []
127
+ @cpu = cpu
128
+
129
+ total_bytes_read = 0
130
+
79
131
  instructions.map do |instruction_arr|
80
132
  instruction, length, address = instruction_arr
81
- replace_memory_range(address, address+length-1, instruction)
82
- disassembled_instructions << disassemble_range(address, 1, length > 2).join
133
+ total_bytes_read += replace_memory_range(address, address+length-1, instruction)
134
+ end
135
+
136
+ raw_bytes.each do |raw_byte|
137
+ addr, bytes = raw_byte
138
+ total_bytes_read += replace_memory_range(addr, addr + bytes.length - 1, bytes)
139
+ end
140
+
141
+ incbin_files.each do |file|
142
+ addr, filename = file
143
+ total_bytes_read += incbin(filename, addr)
83
144
  end
84
145
 
85
- return disassembled_instructions
146
+ return "Read #{total_bytes_read} bytes"
86
147
  end
87
148
 
88
149
  def detect_opcode_data_from_mnemonic(mnemonic, operand)
89
- Definitions::OPCODES_DATA.detect do |row|
150
+ SnesUtils.const_get(@cpu.capitalize)::Definitions::OPCODES_DATA.detect do |row|
90
151
  mode = row[:mode]
91
- regex = Definitions::MODES_REGEXES[mode]
152
+ regex = SnesUtils.const_get(@cpu.capitalize)::Definitions::MODES_REGEXES[mode]
92
153
  row[:mnemonic] == mnemonic && regex =~ operand
93
154
  end
94
155
  end
95
156
 
96
157
  def detect_opcode_data_from_opcode(opcode, force_length)
97
- Definitions::OPCODES_DATA.detect do |row|
98
- if row[:m]
99
- accumulator_flag = force_length ? 0 : @accumulator_flag
100
- row[:opcode] == opcode && row[:m] == accumulator_flag
101
- elsif row[:x]
102
- index_flag = force_length ? 0 : @index_flag
103
- row[:opcode] == opcode && row[:x] == index_flag
104
- else
158
+ SnesUtils.const_get(@cpu.capitalize)::Definitions::OPCODES_DATA.detect do |row|
159
+ if @cpu == :spc700
105
160
  row[:opcode] == opcode
161
+ else
162
+ if row[:m]
163
+ accumulator_flag = force_length ? 0 : @accumulator_flag
164
+ row[:opcode] == opcode && row[:m] == accumulator_flag
165
+ elsif row[:x]
166
+ index_flag = force_length ? 0 : @index_flag
167
+ row[:opcode] == opcode && row[:x] == index_flag
168
+ else
169
+ row[:opcode] == opcode
170
+ end
106
171
  end
107
172
  end
108
173
  end
@@ -137,7 +202,7 @@ module SnesUtils
137
202
 
138
203
  @memory[start_full_addr..end_full_addr] = bytes
139
204
 
140
- true
205
+ bytes.size
141
206
  end
142
207
 
143
208
  def getline
@@ -150,13 +215,21 @@ module SnesUtils
150
215
  if line == '!'
151
216
  @normal_mode = false
152
217
  return
218
+ elsif line == '.spc700'
219
+ @cpu = :spc700
220
+ return 'spc700'
221
+ elsif line == '.65816'
222
+ @cpu = :wdc65816
223
+ return '65816'
153
224
  elsif matches = Definitions::WRITE_REGEX.match(line)
154
225
  filename = matches[1].strip.chomp
155
226
  out_filename = write(filename)
156
227
  return "Written #{@memory.size} bytes to file #{out_filename}"
157
228
  elsif matches = Definitions::READ_REGEX.match(line)
158
- filename = matches[1].strip.chomp
159
- return read(filename)
229
+ start_addr = matches[2]&.to_i(16)
230
+ filename = matches[3].strip.chomp
231
+
232
+ return read(filename, start_addr)
160
233
  elsif matches = Definitions::INCBIN_REGEX.match(line)
161
234
  start_addr = matches[1].to_i(16)
162
235
  filename = matches[2].strip.chomp
@@ -224,17 +297,44 @@ module SnesUtils
224
297
  end
225
298
  end
226
299
 
227
- def parse_address(line)
300
+ def parse_address(line, register_label = false)
228
301
  return @current_addr if line.index(':').nil?
229
- line.split(':').first.to_i(16)
302
+
303
+ address = line.split(':').first.strip.chomp
304
+ return address.to_i(16) unless address.start_with?('@')
305
+
306
+ @label_registry[address] = hex(@current_addr, 4)
307
+ return @current_addr
230
308
  end
231
309
 
232
- def parse_instruction(line)
233
- current_address = parse_address(line)
310
+ def parse_instruction(line, register_label = false, resolve_label = false)
311
+ current_address = parse_address(line, register_label)
234
312
  instruction = line.split(':').last.split(' ')
235
313
  mnemonic = instruction[0].upcase
236
314
  raw_operand = instruction[1].to_s
237
315
 
316
+ if register_label and raw_operand.include?('@')
317
+ raw_operands = raw_operand.split(',')
318
+ raw_operands.each_with_index do |op, idx|
319
+ if op.start_with?('@')
320
+ raw_operands[idx] = hex(@current_addr, 4)
321
+ end
322
+ end
323
+
324
+ raw_operand = raw_operands.join(',')
325
+ end
326
+
327
+ if resolve_label and raw_operand.include?('@')
328
+ raw_operands = raw_operand.split(',')
329
+ raw_operands.each_with_index do |op, idx|
330
+ if op.start_with?('@')
331
+ raw_operands[idx] = @label_registry[op]
332
+ end
333
+ end
334
+
335
+ raw_operand = raw_operands.join(',')
336
+ end
337
+
238
338
  opcode_data = detect_opcode_data_from_mnemonic(mnemonic, raw_operand)
239
339
 
240
340
  return unless opcode_data
@@ -243,22 +343,48 @@ module SnesUtils
243
343
  mode = opcode_data[:mode]
244
344
  length = opcode_data[:length]
245
345
 
246
- operand_matches = Definitions::MODES_REGEXES[mode].match(raw_operand)
247
- if mode == :bm
248
- operand = "#{operand_matches[1]}#{operand_matches[2]}".to_i(16)
346
+ operand_matches = SnesUtils.const_get(@cpu.capitalize)::Definitions::MODES_REGEXES[mode].match(raw_operand)
347
+
348
+ if SnesUtils.const_get(@cpu.capitalize)::Definitions::DOUBLE_OPERAND_INSTRUCTIONS.include?(mode)
349
+ if SnesUtils.const_get(@cpu.capitalize)::Definitions::BIT_INSTRUCTIONS.include?(mode)
350
+ m = operand_matches[1].to_i(16)
351
+ return if m > 0x1fff
352
+ b = operand_matches[2].to_i(16)
353
+ return if b > 7
354
+
355
+ operand = m << 3 | 5
356
+ else
357
+ if SnesUtils.const_get(@cpu.capitalize)::Definitions::REL_INSTRUCTIONS.include?(mode)
358
+ operand = [operand_matches[1], operand_matches[2]].map { |o| o.to_i(16) }
359
+ else
360
+ operand = "#{operand_matches[1]}#{operand_matches[2]}".to_i(16)
361
+ end
362
+ end
249
363
  else
250
364
  operand = operand_matches[1]&.to_i(16)
251
365
  end
252
366
 
253
367
  if operand
254
- if %i[rel rell].include? mode
255
- relative_addr = operand - current_address - length
256
- return if mode == :rel && (relative_addr < -128 || relative_addr > 127)
257
- return if mode == :rell && (relative_addr < -32768 || relative_addr > 32767)
368
+ if SnesUtils.const_get(@cpu.capitalize)::Definitions::REL_INSTRUCTIONS.include?(mode)
369
+ if SnesUtils.const_get(@cpu.capitalize)::Definitions::DOUBLE_OPERAND_INSTRUCTIONS.include?(mode)
370
+ relative_addr = operand[1] - current_address - length
258
371
 
259
- relative_addr = (2**(8*(length-1))) + relative_addr if relative_addr < 0
372
+ return if (relative_addr < -128 || relative_addr > 127)
260
373
 
261
- param_bytes = hex(relative_addr, 2*(length-1)).scan(/.{2}/).reverse.join
374
+ relative_addr = 0x100 + relative_addr if relative_addr < 0
375
+ param_bytes = "#{hex(operand[0])}#{hex(relative_addr)}"
376
+ else
377
+ relative_addr = operand - current_address - length
378
+
379
+ if @cpu == :wdc65816 && mode == :rell
380
+ return if (relative_addr < -32768 || relative_addr > 32767)
381
+ else
382
+ return if (relative_addr < -128 || relative_addr > 127)
383
+ end
384
+
385
+ relative_addr = (2**(8*(length-1))) + relative_addr if relative_addr < 0
386
+ param_bytes = hex(relative_addr, 2*(length-1)).scan(/.{2}/).reverse.join
387
+ end
262
388
  else
263
389
  param_bytes = hex(operand, 2*(length-1)).scan(/.{2}/).reverse.join
264
390
  end
@@ -293,26 +419,40 @@ module SnesUtils
293
419
  mode = opcode_data[:mode]
294
420
  length = opcode_data[:length]
295
421
 
296
- format = Definitions::MODES_FORMATS[mode]
422
+ format = SnesUtils.const_get(@cpu.capitalize)::Definitions::MODES_FORMATS[mode]
297
423
 
298
424
  operand = memory_range(next_idx+1, next_idx+length-1).reverse.join.to_i(16)
299
425
 
300
426
  hex_encoded_instruction = memory_range(next_idx, next_idx+length-1)
301
427
  prefix = ["#{address_human(next_idx)}:", *hex_encoded_instruction].join(' ')
302
428
 
303
- auto_update_flags(opcode, operand)
304
-
305
- if mode == :bm
306
- operand = operand.to_s(16).scan(/.{2}/).map { |op| op.to_i(16) }
307
- elsif %i[rel rell].include? mode
308
- limit = mode == :rel ? 0x7f : 0x7fff
309
- offset = mode == :rel ? 0x100 : 0x10000
310
- rjust_len = mode == :rel ? 2 : 4
311
- relative_addr = operand > limit ? operand - offset : operand
312
- relative_addr_s = "#{relative_addr.positive? ? '+' : '-'}#{hex(relative_addr.abs, rjust_len)}"
313
- absolute_addr = next_idx + length + relative_addr
314
- absolute_addr += 0x10000 if absolute_addr.negative?
315
- operand = [absolute_addr, relative_addr_s]
429
+ auto_update_flags(opcode, operand) if @cpu == :wdc65816
430
+
431
+ if SnesUtils.const_get(@cpu.capitalize)::Definitions::DOUBLE_OPERAND_INSTRUCTIONS.include?(mode)
432
+ if SnesUtils.const_get(@cpu.capitalize)::Definitions::BIT_INSTRUCTIONS.include?(mode)
433
+ m = operand >> 3
434
+ b = operand & 0b111
435
+ operand = [m, b]
436
+ else
437
+ operand = hex(operand, 4).scan(/.{2}/).map { |op| op.to_i(16) }
438
+ if @cpu == :spc700 && SnesUtils.const_get(@cpu.capitalize)::Definitions::REL_INSTRUCTIONS.include?(mode)
439
+ r = operand.first
440
+ r_operand = relative_operand(r, next_idx + length)
441
+
442
+ operand = [operand.last, *r_operand]
443
+ end
444
+ end
445
+ elsif SnesUtils.const_get(@cpu.capitalize)::Definitions::SINGLE_OPERAND_INSTRUCTIONS.include?(mode)
446
+ if SnesUtils.const_get(@cpu.capitalize)::Definitions::REL_INSTRUCTIONS.include?(mode)
447
+ if @cpu == :wdc65816 && SnesUtils.const_get(@cpu.capitalize)::Definitions::REL_INSTRUCTIONS.include?(mode)
448
+ limit = mode == :rel ? 0x7f : 0x7fff
449
+ offset = mode == :rel ? 0x100 : 0x10000
450
+ rjust_len = mode == :rel ? 2 : 4
451
+ operand = relative_operand(operand, next_idx + length, limit, offset, rjust_len)
452
+ else
453
+ operand = relative_operand(operand, next_idx + length)
454
+ end
455
+ end
316
456
  end
317
457
 
318
458
  instructions << "#{prefix.ljust(30)} #{format % [mnemonic, *operand]}"
@@ -322,5 +462,14 @@ module SnesUtils
322
462
  @next_addr_to_list = next_idx
323
463
  return instructions
324
464
  end
465
+
466
+ def relative_operand(operand, next_idx, limit = 0x7f, offset = 0x100, rjust_len = 2)
467
+ relative_addr = operand > limit ? operand - offset : operand
468
+ relative_addr_s = "#{relative_addr.positive? ? '+' : '-'}#{hex(relative_addr.abs, rjust_len)}"
469
+ absolute_addr = next_idx + relative_addr
470
+ absolute_addr += 0x10000 if absolute_addr.negative?
471
+
472
+ [absolute_addr, relative_addr_s]
473
+ end
325
474
  end
326
475
  end