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.
- checksums.yaml +4 -4
- data/bin/mini_assembler +10 -0
- data/lib/mini_assembler/definitions.rb +1 -333
- data/lib/mini_assembler/mini_assembler.rb +203 -54
- data/lib/mini_assembler/spc700/definitions.rb +470 -0
- data/lib/mini_assembler/wdc65816/definitions.rb +344 -0
- data/lib/snes_utils.rb +2 -0
- metadata +10 -8
@@ -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
|
64
|
+
def assemble_file(filename, outfile = 'out.smc')
|
61
65
|
return 0 unless File.file?(filename)
|
62
66
|
|
63
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
113
|
+
incbin_files << [addr, filename]
|
114
|
+
end
|
115
|
+
|
116
|
+
next
|
117
|
+
end
|
70
118
|
|
71
|
-
|
72
|
-
|
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
|
-
|
75
|
-
|
122
|
+
instructions << [instruction, length, address]
|
123
|
+
@current_addr = address + length
|
124
|
+
end
|
76
125
|
end
|
77
126
|
|
78
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
159
|
-
|
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
|
-
|
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
|
-
|
248
|
-
|
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
|
255
|
-
|
256
|
-
|
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
|
-
|
372
|
+
return if (relative_addr < -128 || relative_addr > 127)
|
260
373
|
|
261
|
-
|
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
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
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
|