snes_utils 0.2.0 → 0.3.0
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/png2snes +12 -1
- data/bin/vas +2 -1
- data/lib/mini_assembler/superfx/definitions.rb +158 -0
- data/lib/png2snes/png2snes.rb +51 -20
- data/lib/snes_utils.rb +1 -2
- data/lib/vas/vas.rb +230 -28
- data/spec/mini_assembler_spec.rb +136 -0
- data/spec/spc700_spec.rb +322 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/vas_spec.rb +5 -0
- data/spec/wdc65816_spec.rb +322 -0
- metadata +18 -12
data/lib/vas/vas.rb
CHANGED
@@ -4,19 +4,24 @@ module SnesUtils
|
|
4
4
|
class Vas
|
5
5
|
WDC65816 = :wdc65816
|
6
6
|
SPC700 = :spc700
|
7
|
+
SUPERFX = :superfx
|
7
8
|
|
8
9
|
DIRECTIVE = [
|
9
|
-
'.65816', '.spc700', '.org', '.base', '.db', '.rb', '.incbin'
|
10
|
+
'.65816', '.spc700', '.superfx', '.org', '.base', '.db', '.rb', '.incbin'
|
10
11
|
]
|
11
12
|
|
12
13
|
LABEL_OPERATORS = ['@', '!', '<', '>', '\^']
|
13
14
|
|
14
|
-
def initialize(filename)
|
15
|
+
def initialize(filename, outfile)
|
15
16
|
raise "File not found: #{filename}" unless File.file?(filename)
|
16
17
|
|
17
18
|
@filename = filename
|
18
|
-
@
|
19
|
+
@outfile = outfile
|
20
|
+
@file = []
|
19
21
|
@label_registry = []
|
22
|
+
@reading_macro = false
|
23
|
+
@current_macro = nil
|
24
|
+
@macros_registry = {}
|
20
25
|
@define_registry = {}
|
21
26
|
@incbin_list = []
|
22
27
|
@byte_sequence_list = []
|
@@ -38,7 +43,7 @@ module SnesUtils
|
|
38
43
|
write_label_registry
|
39
44
|
insert_bytes
|
40
45
|
incbin
|
41
|
-
write
|
46
|
+
write(@outfile)
|
42
47
|
end
|
43
48
|
|
44
49
|
def construct_file(filename = @filename)
|
@@ -47,27 +52,101 @@ module SnesUtils
|
|
47
52
|
next if line.empty?
|
48
53
|
|
49
54
|
if line.start_with?('.include')
|
55
|
+
raise "can't include file within macro" if @reading_macro
|
56
|
+
|
50
57
|
directive = line.split(' ')
|
51
58
|
inc_filename = directive[1].to_s.strip.chomp
|
59
|
+
dir = File.dirname(filename)
|
52
60
|
|
53
|
-
construct_file(inc_filename)
|
61
|
+
construct_file(File.join(dir, inc_filename))
|
54
62
|
elsif line.start_with?('.define')
|
63
|
+
raise "can't define variable within macro" if @reading_macro
|
64
|
+
|
55
65
|
args = line.split(' ')
|
56
66
|
key = "#{args[1]}"
|
57
|
-
|
67
|
+
raw_val = args[2..-1].join(' ').split(';').first
|
68
|
+
raise "Missing value for : #{key}" if raw_val.nil?
|
69
|
+
|
70
|
+
val = raw_val.strip.chomp
|
58
71
|
|
59
72
|
raise "Already defined: #{key}" unless @define_registry[key].nil?
|
60
73
|
@define_registry[key] = val
|
74
|
+
elsif line.start_with?('.call')
|
75
|
+
raise "can't call macro within macro" if @reading_macro
|
76
|
+
|
77
|
+
args = line.split(' ')
|
78
|
+
macro_name = args[1]
|
79
|
+
macro_args = args[2..-1].join.split(',')
|
80
|
+
call_macro(macro_name, macro_args, line_no + 1)
|
81
|
+
elsif line.start_with?('.macro')
|
82
|
+
raise "can't have nested macro" if @reading_macro
|
83
|
+
|
84
|
+
args = line.split(' ')
|
85
|
+
macro_name = args[1]
|
86
|
+
macro_args = args[2..-1].join.split(',')
|
87
|
+
init_macro(macro_name, macro_args)
|
61
88
|
else
|
62
89
|
new_line = replace_define(line)
|
90
|
+
line_info = { line: new_line, orig_line: line, line_no: line_no + 1, filename: filename }
|
63
91
|
|
64
|
-
@
|
92
|
+
if @reading_macro
|
93
|
+
line.start_with?('.endm') ? save_macro : @current_macro[:lines] << line_info
|
94
|
+
else
|
95
|
+
@file << line_info
|
96
|
+
end
|
65
97
|
end
|
66
98
|
end
|
99
|
+
end
|
67
100
|
|
101
|
+
def init_macro(name, args)
|
102
|
+
@current_macro = { name: name, args: args, lines: [] }
|
103
|
+
@reading_macro = true
|
104
|
+
end
|
105
|
+
|
106
|
+
def save_macro
|
107
|
+
name = @current_macro[:name]
|
108
|
+
raise "macro `#{name}` already defined" unless @macros_registry[name].nil?
|
109
|
+
@macros_registry[name] = @current_macro
|
110
|
+
@reading_macro = false
|
111
|
+
end
|
112
|
+
|
113
|
+
def call_macro(name, raw_args, line_no)
|
114
|
+
macro = @macros_registry[name]
|
115
|
+
uuid = SecureRandom.uuid
|
116
|
+
raise "line #{line_no}: call of undefined macro `#{name}`" if macro.nil?
|
117
|
+
|
118
|
+
args_names = @macros_registry[name][:args]
|
119
|
+
if args_names.count != raw_args.count
|
120
|
+
raise "line #{line_no}: wrong number of arguments for macro `#{name}` expected : #{args_names.count}, given: #{raw_args.count}"
|
121
|
+
end
|
122
|
+
args = {}
|
123
|
+
args_names.count.times do |i|
|
124
|
+
args[args_names[i]] = raw_args[i]
|
125
|
+
end
|
126
|
+
|
127
|
+
macro[:lines].each_with_index do |line_info|
|
128
|
+
line = line_info[:line]
|
129
|
+
|
130
|
+
if line.include?('%')
|
131
|
+
# replace variable with arg
|
132
|
+
matches = line.match(/%(\w+)%?/)
|
133
|
+
if matches[1] == 'MACRO_ID'
|
134
|
+
value = uuid.delete('-')
|
135
|
+
else
|
136
|
+
value = args[matches[1]]
|
137
|
+
end
|
138
|
+
raise "line #{line_no}: undefined variable `#{matches[1]}` for macro `#{name}`" if value.nil?
|
139
|
+
replaced_line = line.gsub(/#{matches[0]}/, value)
|
140
|
+
@file << line_info.merge(line: replace_define(replaced_line))
|
141
|
+
else
|
142
|
+
@file << line_info
|
143
|
+
end
|
144
|
+
end
|
68
145
|
end
|
69
146
|
|
70
147
|
def replace_define(line)
|
148
|
+
# TODO also support multiple define on same line
|
149
|
+
# hint : generalize replace_eval_label ?
|
71
150
|
found = nil
|
72
151
|
|
73
152
|
@define_registry.keys.each do |key|
|
@@ -77,17 +156,16 @@ module SnesUtils
|
|
77
156
|
end
|
78
157
|
end
|
79
158
|
|
80
|
-
|
81
159
|
return line if found.nil?
|
82
160
|
|
83
161
|
val = @define_registry[found]
|
84
162
|
|
85
|
-
line.gsub(/\b#{found}/, val)
|
163
|
+
line.gsub(/\b#{found}\b/, val)
|
86
164
|
end
|
87
165
|
|
88
166
|
def assemble_file(pass)
|
89
|
-
@file.each do |
|
90
|
-
@line =
|
167
|
+
@file.each do |line|
|
168
|
+
@line = line[:line]
|
91
169
|
|
92
170
|
if @line.include?(':')
|
93
171
|
arr = @line.split(':')
|
@@ -95,7 +173,7 @@ module SnesUtils
|
|
95
173
|
unless /^\w+$/ =~ label
|
96
174
|
raise "Invalid label: #{label}"
|
97
175
|
end
|
98
|
-
register_label(label) if pass == 0
|
176
|
+
register_label(label, pass) # if pass == 0
|
99
177
|
next unless arr[1]
|
100
178
|
instruction = arr[1].strip.chomp
|
101
179
|
else
|
@@ -105,14 +183,14 @@ module SnesUtils
|
|
105
183
|
next if instruction.empty?
|
106
184
|
|
107
185
|
if instruction.start_with?(*DIRECTIVE)
|
108
|
-
process_directive(instruction, pass)
|
186
|
+
process_directive(instruction, pass, line)
|
109
187
|
next
|
110
188
|
end
|
111
189
|
|
112
190
|
begin
|
113
191
|
bytes = LineAssembler.new(instruction, **options).assemble
|
114
192
|
rescue => e
|
115
|
-
puts "Error at line #{
|
193
|
+
puts "Error at line #{line[:filename]}##{line[:line_no]} - (#{line[:orig_line]}) : #{e}"
|
116
194
|
exit(1)
|
117
195
|
end
|
118
196
|
|
@@ -121,9 +199,14 @@ module SnesUtils
|
|
121
199
|
end
|
122
200
|
end
|
123
201
|
|
124
|
-
def register_label(label)
|
125
|
-
|
126
|
-
|
202
|
+
def register_label(label, pass)
|
203
|
+
if pass == 0
|
204
|
+
raise "Label already defined: #{label}" if @label_registry.detect { |l| l[0] == label }
|
205
|
+
@label_registry << [label, @program_counter + @origin]
|
206
|
+
else
|
207
|
+
index = @label_registry.index { |l| l[0] == label }
|
208
|
+
@label_registry[index][1] = @program_counter + @origin
|
209
|
+
end
|
127
210
|
end
|
128
211
|
|
129
212
|
def insert(bytes, insert_at = insert_index)
|
@@ -134,7 +217,12 @@ module SnesUtils
|
|
134
217
|
@program_counter + @base
|
135
218
|
end
|
136
219
|
|
137
|
-
def write(filename
|
220
|
+
def write(filename)
|
221
|
+
if filename.nil?
|
222
|
+
dir = File.dirname(@filename)
|
223
|
+
filename = File.join(dir, 'out.sfc')
|
224
|
+
end
|
225
|
+
|
138
226
|
File.open(filename, 'w+b') do |file|
|
139
227
|
file.write([@memory.map { |i| Vas::hex(i) }.join].pack('H*'))
|
140
228
|
end
|
@@ -155,7 +243,7 @@ module SnesUtils
|
|
155
243
|
}
|
156
244
|
end
|
157
245
|
|
158
|
-
def process_directive(instruction, pass)
|
246
|
+
def process_directive(instruction, pass, line_info)
|
159
247
|
directive = instruction.split(' ')
|
160
248
|
|
161
249
|
case directive[0]
|
@@ -163,21 +251,54 @@ module SnesUtils
|
|
163
251
|
@cpu = WDC65816
|
164
252
|
when '.spc700'
|
165
253
|
@cpu = SPC700
|
254
|
+
when '.superfx'
|
255
|
+
@cpu = SUPERFX
|
166
256
|
when '.org'
|
167
257
|
update_origin(directive[1].to_i(16))
|
168
258
|
when '.base'
|
169
259
|
@base = directive[1].to_i(16)
|
170
260
|
when '.incbin'
|
171
|
-
|
261
|
+
inc_filename = directive[1].to_s.strip.chomp
|
262
|
+
dir = File.dirname(line_info[:filename])
|
263
|
+
@program_counter += prepare_incbin(File.join(dir, inc_filename), pass)
|
172
264
|
when '.db'
|
173
265
|
raw_line = directive[1..-1].join.to_s.strip.chomp
|
174
266
|
line = LineAssembler.new(raw_line, **options).replace_labels(raw_line)
|
175
267
|
|
176
268
|
@program_counter += define_bytes(line, pass)
|
177
269
|
when '.rb'
|
178
|
-
|
179
|
-
|
180
|
-
|
270
|
+
arg = directive[1..-1].join
|
271
|
+
|
272
|
+
count = self.class.replace_eval_label(@label_registry, arg)
|
273
|
+
@program_counter += count.is_a?(String) ? count.to_i(16) : count
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def self.replace_eval_label(registry, arg)
|
278
|
+
return arg unless matches = /({(.*)})(w)?$/.match(arg)
|
279
|
+
|
280
|
+
found = {}
|
281
|
+
|
282
|
+
registry.each do |key, val|
|
283
|
+
if arg.match(/\b#{key}\b/)
|
284
|
+
found[key] = val
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
return arg.to_i(16) if found.empty?
|
289
|
+
|
290
|
+
new_arg = matches[2]
|
291
|
+
|
292
|
+
found.each do |key, val|
|
293
|
+
new_arg = new_arg.gsub(/\b#{key}\b/, val.to_s)
|
294
|
+
end
|
295
|
+
|
296
|
+
if matches[3] == 'w'
|
297
|
+
res = eval(new_arg).to_s(16).rjust(4, '0')[-4..-1]
|
298
|
+
arg[0..-2].gsub(matches[1], res)
|
299
|
+
else
|
300
|
+
res = eval(new_arg).to_s(16).rjust(2, '0')[-2..-1]
|
301
|
+
arg.gsub(matches[1], res)
|
181
302
|
end
|
182
303
|
end
|
183
304
|
|
@@ -230,7 +351,13 @@ module SnesUtils
|
|
230
351
|
def write_label_registry
|
231
352
|
longest = @label_registry.map{|r| r[0] }.max_by(&:length)
|
232
353
|
|
233
|
-
|
354
|
+
if @outfile.nil?
|
355
|
+
dir = File.dirname(@filename)
|
356
|
+
else
|
357
|
+
dir = File.dirname(@outfile)
|
358
|
+
end
|
359
|
+
|
360
|
+
File.open(File.join(dir, 'labels.txt'), 'w+b') do |file|
|
234
361
|
@label_registry.each do |label|
|
235
362
|
adjusted_label = label[0].ljust(longest.length, ' ')
|
236
363
|
raw_address = Vas::hex(label[1], 6)
|
@@ -238,6 +365,21 @@ module SnesUtils
|
|
238
365
|
file.write "#{adjusted_label} #{address}\n"
|
239
366
|
end
|
240
367
|
end
|
368
|
+
File.open(File.join(dir, 'labels.msl'), 'w+b') do |file|
|
369
|
+
@label_registry.each do |label|
|
370
|
+
if label[1] >= 0x7e0000 && label[1] <= 0x7fffff
|
371
|
+
bank = label[1] & 0xff0000
|
372
|
+
address = "WORK:#{Vas::hex(label[1] - bank)}:#{label[0]}:"
|
373
|
+
else
|
374
|
+
bank = label[1] & 0xff0000
|
375
|
+
bank_i = bank >> 16 & 0xf
|
376
|
+
# low rom only for now
|
377
|
+
prg_addr = label[1] - bank - 0x8000 + bank_i * 0x8000
|
378
|
+
address = "PRG:#{Vas::hex(prg_addr)}:#{label[0]}:"
|
379
|
+
end
|
380
|
+
file.write "#{address}\n"
|
381
|
+
end
|
382
|
+
end
|
241
383
|
end
|
242
384
|
end
|
243
385
|
|
@@ -254,10 +396,11 @@ module SnesUtils
|
|
254
396
|
mnemonic = instruction[0].upcase
|
255
397
|
raw_operand = instruction[1].to_s
|
256
398
|
|
257
|
-
raw_operand =
|
399
|
+
raw_operand = Vas.replace_eval_label(@label_registry, raw_operand)
|
400
|
+
raw_operand = replace_label(raw_operand).downcase # TODO -> generalize replace_label_eval
|
258
401
|
|
259
402
|
opcode_data = detect_opcode(mnemonic, raw_operand)
|
260
|
-
raise "Invalid syntax" unless opcode_data
|
403
|
+
raise "Invalid syntax #{@line}" unless opcode_data
|
261
404
|
|
262
405
|
opcode = opcode_data[:opcode]
|
263
406
|
@mode = opcode_data[:mode]
|
@@ -265,6 +408,8 @@ module SnesUtils
|
|
265
408
|
|
266
409
|
operand_data = detect_operand(raw_operand)
|
267
410
|
|
411
|
+
return process_fx_instruction(opcode_data, operand_data) if special_fx_instruction?(opcode_data[:alt])
|
412
|
+
|
268
413
|
operand = process_operand(operand_data)
|
269
414
|
|
270
415
|
return [opcode, *operand]
|
@@ -304,7 +449,7 @@ module SnesUtils
|
|
304
449
|
value = value & 0x00ffff
|
305
450
|
new_value = Vas::hex(value, 4)
|
306
451
|
when '!'
|
307
|
-
value = value | (((@current_address >> 16) & 0xff) << 16)
|
452
|
+
value = value # | (((@current_address >> 16) & 0xff) << 16) # BUG HERE
|
308
453
|
new_value = Vas::hex(value, 6)
|
309
454
|
when '<'
|
310
455
|
value = value & 0x0000ff
|
@@ -344,6 +489,48 @@ module SnesUtils
|
|
344
489
|
end
|
345
490
|
end
|
346
491
|
|
492
|
+
def process_fx_instruction(opcode_data, operand_data)
|
493
|
+
alt = opcode_data[:alt]
|
494
|
+
|
495
|
+
return [alt, opcode_data[:opcode]].compact if @mode == :imp
|
496
|
+
|
497
|
+
if fx_mov_instruction?
|
498
|
+
reg1 = operand_data[1].to_i(10)
|
499
|
+
raise "Invalid register R#{reg1}" if reg1 > 15
|
500
|
+
reg2 = operand_data[2].to_i(10)
|
501
|
+
raise "Invalid register R#{reg2}" if reg2 > 15
|
502
|
+
|
503
|
+
raw_opcode = opcode_data[:opcode]
|
504
|
+
tmp_opcode = raw_opcode | (reg2 << 8) | reg1
|
505
|
+
opcode = [((tmp_opcode >> 8) & 0xff), ((tmp_opcode >> 0) & 0xff)]
|
506
|
+
|
507
|
+
operand = nil
|
508
|
+
else
|
509
|
+
base = @mode == :imm4 ? 16 : 10
|
510
|
+
index = inverted_dest_instruction? ? 2 : 1
|
511
|
+
|
512
|
+
opcode_suffix = operand_data[index].to_i(base)
|
513
|
+
opcode_prefix = opcode_data[:opcode]
|
514
|
+
opcode = (opcode_prefix << 4) | opcode_suffix
|
515
|
+
|
516
|
+
operand = process_fx_operand(operand_data, alt.nil? ? 0 : 1)
|
517
|
+
end
|
518
|
+
|
519
|
+
[alt, *opcode, *operand].compact
|
520
|
+
end
|
521
|
+
|
522
|
+
def process_fx_operand(operand_data, alt)
|
523
|
+
return unless double_operand_instruction?
|
524
|
+
|
525
|
+
index = inverted_dest_instruction? ? 1 : 2
|
526
|
+
operand = operand_data[index]&.to_i(16)
|
527
|
+
|
528
|
+
raise 'Invalid address, must be multiple of 2' if short_addr_instruction? && operand.odd?
|
529
|
+
operand /= 2 if short_addr_instruction?
|
530
|
+
|
531
|
+
little_endian(operand, @length - 1 - alt)
|
532
|
+
end
|
533
|
+
|
347
534
|
def process_double_operand_instruction(operand_data)
|
348
535
|
if bit_instruction?
|
349
536
|
process_bit_operand(operand_data)
|
@@ -379,7 +566,6 @@ module SnesUtils
|
|
379
566
|
relative_addr += 0x100 if relative_addr < 0
|
380
567
|
relative_addr
|
381
568
|
end
|
382
|
-
|
383
569
|
end
|
384
570
|
|
385
571
|
def little_endian(operand, length)
|
@@ -403,5 +589,21 @@ module SnesUtils
|
|
403
589
|
def rel_instruction?
|
404
590
|
SnesUtils.const_get(@cpu.capitalize)::Definitions::REL_INSTRUCTIONS.include?(@mode)
|
405
591
|
end
|
592
|
+
|
593
|
+
def special_fx_instruction?(alt)
|
594
|
+
@cpu == Vas::SUPERFX && (SnesUtils.const_get(@cpu.capitalize)::Definitions::SFX_INSTRUCTIONS.include?(@mode) || !alt.nil?)
|
595
|
+
end
|
596
|
+
|
597
|
+
def fx_mov_instruction?
|
598
|
+
@cpu == Vas::SUPERFX && SnesUtils.const_get(@cpu.capitalize)::Definitions::MOV_INSTRUCTIONS.include?(@mode)
|
599
|
+
end
|
600
|
+
|
601
|
+
def short_addr_instruction?
|
602
|
+
@cpu == Vas::SUPERFX && SnesUtils.const_get(@cpu.capitalize)::Definitions::SHORT_ADDR_INSTRUCTIONS.include?(@mode)
|
603
|
+
end
|
604
|
+
|
605
|
+
def inverted_dest_instruction?
|
606
|
+
@cpu == Vas::SUPERFX && SnesUtils.const_get(@cpu.capitalize)::Definitions::INV_DEST_INSTRUCTIONS.include?(@mode)
|
607
|
+
end
|
406
608
|
end
|
407
609
|
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'snes_utils'
|
3
|
+
|
4
|
+
describe SnesUtils::MiniAssembler do
|
5
|
+
let(:mini_asm) { SnesUtils::MiniAssembler.new }
|
6
|
+
|
7
|
+
describe '#parse_address' do
|
8
|
+
subject { mini_asm.parse_address(line) }
|
9
|
+
|
10
|
+
context 'when address is given' do
|
11
|
+
let(:line) { '300: lda #$12' }
|
12
|
+
|
13
|
+
it 'returns the address given' do
|
14
|
+
expect(subject).to eq(0x300)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when address is implied' do
|
19
|
+
let(:line) { 'lda #$12' }
|
20
|
+
|
21
|
+
it 'returns the current address' do
|
22
|
+
expect(subject).to eq(0)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#parse_instruction' do
|
28
|
+
subject { mini_asm.parse_instruction(line) }
|
29
|
+
context 'imm8' do
|
30
|
+
let(:line) { 'ora #17' }
|
31
|
+
|
32
|
+
it do
|
33
|
+
expect(subject).to eq [['09', '17'], 2, 0]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'abs' do
|
38
|
+
let(:line) { 'lda $1234' }
|
39
|
+
|
40
|
+
it do
|
41
|
+
expect(subject).to eq [['AD','34','12'], 3, 0]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'imp' do
|
46
|
+
let(:line) { '300:xce' }
|
47
|
+
|
48
|
+
it do
|
49
|
+
expect(subject).to eq [['FB'], 1, 0x300]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'idly' do
|
54
|
+
let(:line) { 'ADC [$12],Y' }
|
55
|
+
|
56
|
+
it do
|
57
|
+
expect(subject).to eq [['77', '12'], 2, 0]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'bm' do
|
62
|
+
let(:line) { 'MVN $12,$34' }
|
63
|
+
|
64
|
+
it do
|
65
|
+
expect(subject).to eq [['54', '34', '12'], 3, 0]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'relative addressing' do
|
70
|
+
context '8 bit rel postivie' do
|
71
|
+
let(:line) { '300:BMI 305' }
|
72
|
+
|
73
|
+
it { expect(subject).to eq [['30', '03'], 2, 0x300] }
|
74
|
+
end
|
75
|
+
|
76
|
+
context '8 bit rel negative' do
|
77
|
+
let(:line) { '30a:bcc 300' }
|
78
|
+
|
79
|
+
it { expect(subject).to eq [['90', 'F4'], 2, 0x30a] }
|
80
|
+
end
|
81
|
+
|
82
|
+
context '16 bit rel postivie' do
|
83
|
+
let(:line) { '304:BRL $03A0' }
|
84
|
+
|
85
|
+
it { expect(subject).to eq [['82', '99', '00'], 3, 0x304] }
|
86
|
+
end
|
87
|
+
|
88
|
+
context '16 bit rel negative' do
|
89
|
+
let(:line) { '3bf:per 37a' }
|
90
|
+
|
91
|
+
it { expect(subject).to eq [['62', 'B8', 'FF'], 3, 0x3bf] }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '#disassemble_range' do
|
97
|
+
subject { mini_asm.disassemble_range(0, count) }
|
98
|
+
|
99
|
+
let(:count) { 1 }
|
100
|
+
|
101
|
+
before { mini_asm.instance_variable_set(:@memory, memory) }
|
102
|
+
|
103
|
+
context 'when cpu is 65816' do
|
104
|
+
let(:memory) { ['82', '99', '00'] }
|
105
|
+
|
106
|
+
it { expect(subject).to eq ['00/0000: 82 99 00 BRL 009C {+0099}'] }
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'when cpu is spc700' do
|
110
|
+
let(:memory) { ['00'] }
|
111
|
+
|
112
|
+
before { mini_asm.instance_variable_set(:@cpu, :spc700) }
|
113
|
+
|
114
|
+
it { expect(subject).to eq ['00/0000: 00 NOP'] }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#read' do
|
119
|
+
let(:file) { 'spec/fixtures/test.asm' }
|
120
|
+
before { mini_asm.read(file) }
|
121
|
+
let(:memory) do
|
122
|
+
["A9", "12", "78", "18", "C9", "14", "3A", "D0",
|
123
|
+
"FD", "00", "00", "80", "02", "42", "12", "AD",
|
124
|
+
"34", "12", "80", "F2", nil, nil, nil, nil,
|
125
|
+
nil, nil, nil, nil, nil, nil, nil, nil,
|
126
|
+
"E4", "12", "4F", "12", "50", "FC", "53", "12",
|
127
|
+
"F9", "FA", "22", "11", nil, nil, nil, nil,
|
128
|
+
"11", "22", "33", "44", "55", "66", "77", "88",
|
129
|
+
"DE", "AF", "FA", "CE", "DE", "CA", "FE", "12"]
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'sets memory correctly' do
|
133
|
+
expect(mini_asm.instance_variable_get(:@memory)).to eq memory
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|