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