snes_utils 0.1.0 → 0.1.1

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.
@@ -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