snes_utils 0.1.1 → 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 +20 -1
- data/bin/tmx2snes +2 -1
- data/bin/vas +17 -0
- data/lib/mini_assembler/definitions.rb +16 -9
- data/lib/mini_assembler/mini_assembler.rb +236 -88
- data/lib/mini_assembler/spc700/definitions.rb +457 -455
- data/lib/mini_assembler/superfx/definitions.rb +158 -0
- data/lib/mini_assembler/wdc65816/definitions.rb +61 -59
- data/lib/png2snes/png2snes.rb +53 -21
- data/lib/snes_utils.rb +4 -4
- data/lib/tmx2snes/tmx2snes.rb +2 -7
- data/lib/vas/vas.rb +609 -0
- 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 +21 -26
data/lib/vas/vas.rb
ADDED
@@ -0,0 +1,609 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module SnesUtils
|
4
|
+
class Vas
|
5
|
+
WDC65816 = :wdc65816
|
6
|
+
SPC700 = :spc700
|
7
|
+
SUPERFX = :superfx
|
8
|
+
|
9
|
+
DIRECTIVE = [
|
10
|
+
'.65816', '.spc700', '.superfx', '.org', '.base', '.db', '.rb', '.incbin'
|
11
|
+
]
|
12
|
+
|
13
|
+
LABEL_OPERATORS = ['@', '!', '<', '>', '\^']
|
14
|
+
|
15
|
+
def initialize(filename, outfile)
|
16
|
+
raise "File not found: #{filename}" unless File.file?(filename)
|
17
|
+
|
18
|
+
@filename = filename
|
19
|
+
@outfile = outfile
|
20
|
+
@file = []
|
21
|
+
@label_registry = []
|
22
|
+
@reading_macro = false
|
23
|
+
@current_macro = nil
|
24
|
+
@macros_registry = {}
|
25
|
+
@define_registry = {}
|
26
|
+
@incbin_list = []
|
27
|
+
@byte_sequence_list = []
|
28
|
+
@memory = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def assemble
|
32
|
+
construct_file
|
33
|
+
|
34
|
+
2.times do |pass|
|
35
|
+
@program_counter = 0
|
36
|
+
@origin = 0
|
37
|
+
@base = 0
|
38
|
+
@cpu = WDC65816
|
39
|
+
|
40
|
+
assemble_file(pass)
|
41
|
+
end
|
42
|
+
|
43
|
+
write_label_registry
|
44
|
+
insert_bytes
|
45
|
+
incbin
|
46
|
+
write(@outfile)
|
47
|
+
end
|
48
|
+
|
49
|
+
def construct_file(filename = @filename)
|
50
|
+
File.open(filename).each_with_index do |raw_line, line_no|
|
51
|
+
line = raw_line.split(';').first.strip.chomp
|
52
|
+
next if line.empty?
|
53
|
+
|
54
|
+
if line.start_with?('.include')
|
55
|
+
raise "can't include file within macro" if @reading_macro
|
56
|
+
|
57
|
+
directive = line.split(' ')
|
58
|
+
inc_filename = directive[1].to_s.strip.chomp
|
59
|
+
dir = File.dirname(filename)
|
60
|
+
|
61
|
+
construct_file(File.join(dir, inc_filename))
|
62
|
+
elsif line.start_with?('.define')
|
63
|
+
raise "can't define variable within macro" if @reading_macro
|
64
|
+
|
65
|
+
args = line.split(' ')
|
66
|
+
key = "#{args[1]}"
|
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
|
71
|
+
|
72
|
+
raise "Already defined: #{key}" unless @define_registry[key].nil?
|
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)
|
88
|
+
else
|
89
|
+
new_line = replace_define(line)
|
90
|
+
line_info = { line: new_line, orig_line: line, line_no: line_no + 1, filename: filename }
|
91
|
+
|
92
|
+
if @reading_macro
|
93
|
+
line.start_with?('.endm') ? save_macro : @current_macro[:lines] << line_info
|
94
|
+
else
|
95
|
+
@file << line_info
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
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
|
145
|
+
end
|
146
|
+
|
147
|
+
def replace_define(line)
|
148
|
+
# TODO also support multiple define on same line
|
149
|
+
# hint : generalize replace_eval_label ?
|
150
|
+
found = nil
|
151
|
+
|
152
|
+
@define_registry.keys.each do |key|
|
153
|
+
if line.match(/\b#{key}\b/)
|
154
|
+
found = key
|
155
|
+
break
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
return line if found.nil?
|
160
|
+
|
161
|
+
val = @define_registry[found]
|
162
|
+
|
163
|
+
line.gsub(/\b#{found}\b/, val)
|
164
|
+
end
|
165
|
+
|
166
|
+
def assemble_file(pass)
|
167
|
+
@file.each do |line|
|
168
|
+
@line = line[:line]
|
169
|
+
|
170
|
+
if @line.include?(':')
|
171
|
+
arr = @line.split(':')
|
172
|
+
label = arr[0].strip.chomp
|
173
|
+
unless /^\w+$/ =~ label
|
174
|
+
raise "Invalid label: #{label}"
|
175
|
+
end
|
176
|
+
register_label(label, pass) # if pass == 0
|
177
|
+
next unless arr[1]
|
178
|
+
instruction = arr[1].strip.chomp
|
179
|
+
else
|
180
|
+
instruction = @line
|
181
|
+
end
|
182
|
+
|
183
|
+
next if instruction.empty?
|
184
|
+
|
185
|
+
if instruction.start_with?(*DIRECTIVE)
|
186
|
+
process_directive(instruction, pass, line)
|
187
|
+
next
|
188
|
+
end
|
189
|
+
|
190
|
+
begin
|
191
|
+
bytes = LineAssembler.new(instruction, **options).assemble
|
192
|
+
rescue => e
|
193
|
+
puts "Error at line #{line[:filename]}##{line[:line_no]} - (#{line[:orig_line]}) : #{e}"
|
194
|
+
exit(1)
|
195
|
+
end
|
196
|
+
|
197
|
+
insert(bytes) if pass == 1
|
198
|
+
@program_counter += bytes.size
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
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
|
210
|
+
end
|
211
|
+
|
212
|
+
def insert(bytes, insert_at = insert_index)
|
213
|
+
@memory[insert_at..insert_at + bytes.size - 1] = bytes
|
214
|
+
end
|
215
|
+
|
216
|
+
def insert_index
|
217
|
+
@program_counter + @base
|
218
|
+
end
|
219
|
+
|
220
|
+
def write(filename)
|
221
|
+
if filename.nil?
|
222
|
+
dir = File.dirname(@filename)
|
223
|
+
filename = File.join(dir, 'out.sfc')
|
224
|
+
end
|
225
|
+
|
226
|
+
File.open(filename, 'w+b') do |file|
|
227
|
+
file.write([@memory.map { |i| Vas::hex(i) }.join].pack('H*'))
|
228
|
+
end
|
229
|
+
|
230
|
+
filename
|
231
|
+
end
|
232
|
+
|
233
|
+
def self.hex(num, rjust_len = 2)
|
234
|
+
(num || 0).to_s(16).rjust(rjust_len, '0').upcase
|
235
|
+
end
|
236
|
+
|
237
|
+
def options
|
238
|
+
{
|
239
|
+
program_counter: @program_counter,
|
240
|
+
origin: @origin,
|
241
|
+
cpu: @cpu,
|
242
|
+
label_registry: @label_registry
|
243
|
+
}
|
244
|
+
end
|
245
|
+
|
246
|
+
def process_directive(instruction, pass, line_info)
|
247
|
+
directive = instruction.split(' ')
|
248
|
+
|
249
|
+
case directive[0]
|
250
|
+
when '.65816'
|
251
|
+
@cpu = WDC65816
|
252
|
+
when '.spc700'
|
253
|
+
@cpu = SPC700
|
254
|
+
when '.superfx'
|
255
|
+
@cpu = SUPERFX
|
256
|
+
when '.org'
|
257
|
+
update_origin(directive[1].to_i(16))
|
258
|
+
when '.base'
|
259
|
+
@base = directive[1].to_i(16)
|
260
|
+
when '.incbin'
|
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)
|
264
|
+
when '.db'
|
265
|
+
raw_line = directive[1..-1].join.to_s.strip.chomp
|
266
|
+
line = LineAssembler.new(raw_line, **options).replace_labels(raw_line)
|
267
|
+
|
268
|
+
@program_counter += define_bytes(line, pass)
|
269
|
+
when '.rb'
|
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)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def update_origin(param)
|
306
|
+
@origin = param
|
307
|
+
@program_counter = 0
|
308
|
+
|
309
|
+
update_base_from_origin
|
310
|
+
end
|
311
|
+
|
312
|
+
def update_base_from_origin
|
313
|
+
# TODO: automatically update base
|
314
|
+
# lorom/hirom scheme
|
315
|
+
# spc700 scheme
|
316
|
+
end
|
317
|
+
|
318
|
+
def prepare_incbin(filename, pass)
|
319
|
+
raise "Incbin: file not found: #{filename}" unless File.file?(filename)
|
320
|
+
|
321
|
+
@incbin_list << [filename, insert_index] if pass == 0
|
322
|
+
File.size(filename) || 0
|
323
|
+
end
|
324
|
+
|
325
|
+
def incbin
|
326
|
+
@incbin_list.each do |filename, index|
|
327
|
+
file = File.open(filename)
|
328
|
+
bytes = file.each_byte.to_a
|
329
|
+
@line = filename
|
330
|
+
insert(bytes, index)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def define_bytes(raw_bytes, pass)
|
335
|
+
bytes = raw_bytes.split(',').map { |rb| rb.scan(/.{2}/).reverse }.flatten.map do |b|
|
336
|
+
bv = b.to_i(16)
|
337
|
+
raise "Invalid byte: #{b} : #{@line}" if bv < 0 || bv > 0xff
|
338
|
+
bv
|
339
|
+
end
|
340
|
+
|
341
|
+
@byte_sequence_list << [bytes, insert_index] if pass == 0
|
342
|
+
bytes.size
|
343
|
+
end
|
344
|
+
|
345
|
+
def insert_bytes
|
346
|
+
@byte_sequence_list.each do |bytes, index|
|
347
|
+
insert(bytes, index)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def write_label_registry
|
352
|
+
longest = @label_registry.map{|r| r[0] }.max_by(&:length)
|
353
|
+
|
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|
|
361
|
+
@label_registry.each do |label|
|
362
|
+
adjusted_label = label[0].ljust(longest.length, ' ')
|
363
|
+
raw_address = Vas::hex(label[1], 6)
|
364
|
+
address = "#{raw_address[0..1]}/#{raw_address[2..-1]}"
|
365
|
+
file.write "#{adjusted_label} #{address}\n"
|
366
|
+
end
|
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
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
class LineAssembler
|
387
|
+
def initialize(raw_line, **options)
|
388
|
+
@line = raw_line.split(';').first.strip.chomp
|
389
|
+
@current_address = (options[:program_counter] + options[:origin])
|
390
|
+
@cpu = options[:cpu]
|
391
|
+
@label_registry = options[:label_registry]
|
392
|
+
end
|
393
|
+
|
394
|
+
def assemble
|
395
|
+
instruction = @line.split(' ')
|
396
|
+
mnemonic = instruction[0].upcase
|
397
|
+
raw_operand = instruction[1].to_s
|
398
|
+
|
399
|
+
raw_operand = Vas.replace_eval_label(@label_registry, raw_operand)
|
400
|
+
raw_operand = replace_label(raw_operand).downcase # TODO -> generalize replace_label_eval
|
401
|
+
|
402
|
+
opcode_data = detect_opcode(mnemonic, raw_operand)
|
403
|
+
raise "Invalid syntax #{@line}" unless opcode_data
|
404
|
+
|
405
|
+
opcode = opcode_data[:opcode]
|
406
|
+
@mode = opcode_data[:mode]
|
407
|
+
@length = opcode_data[:length]
|
408
|
+
|
409
|
+
operand_data = detect_operand(raw_operand)
|
410
|
+
|
411
|
+
return process_fx_instruction(opcode_data, operand_data) if special_fx_instruction?(opcode_data[:alt])
|
412
|
+
|
413
|
+
operand = process_operand(operand_data)
|
414
|
+
|
415
|
+
return [opcode, *operand]
|
416
|
+
end
|
417
|
+
|
418
|
+
def contains_label?(operand)
|
419
|
+
Vas::LABEL_OPERATORS.any? { |s| operand.include?(s[-1,1]) }
|
420
|
+
end
|
421
|
+
|
422
|
+
def replace_labels(operand)
|
423
|
+
while contains_label?(operand)
|
424
|
+
operand = replace_label(operand)
|
425
|
+
end
|
426
|
+
|
427
|
+
operand
|
428
|
+
end
|
429
|
+
|
430
|
+
def replace_label(operand)
|
431
|
+
return operand unless contains_label?(operand)
|
432
|
+
|
433
|
+
unless matches = /(#{Vas::LABEL_OPERATORS.join('|')})(\w+)(\+(\d+))?/.match(operand)
|
434
|
+
raise "Invalid label syntax: #{operand}"
|
435
|
+
end
|
436
|
+
|
437
|
+
mode = matches[1]
|
438
|
+
label = matches[2]
|
439
|
+
offset = matches[4].to_i
|
440
|
+
|
441
|
+
label_data = @label_registry.detect { |l| l[0] == label }
|
442
|
+
|
443
|
+
value = label_data ? label_data[1] : @current_address
|
444
|
+
|
445
|
+
value += offset
|
446
|
+
|
447
|
+
case mode
|
448
|
+
when '@'
|
449
|
+
value = value & 0x00ffff
|
450
|
+
new_value = Vas::hex(value, 4)
|
451
|
+
when '!'
|
452
|
+
value = value # | (((@current_address >> 16) & 0xff) << 16) # BUG HERE
|
453
|
+
new_value = Vas::hex(value, 6)
|
454
|
+
when '<'
|
455
|
+
value = value & 0x0000ff
|
456
|
+
new_value = Vas::hex(value)
|
457
|
+
when '>'
|
458
|
+
value = (value & 0x00ff00) >> 8
|
459
|
+
new_value = Vas::hex(value)
|
460
|
+
when '^'
|
461
|
+
mode = '\^'
|
462
|
+
value = (value & 0xff0000) >> 16
|
463
|
+
new_value = Vas::hex(value)
|
464
|
+
else
|
465
|
+
raise "Mode error: #{mode}"
|
466
|
+
end
|
467
|
+
|
468
|
+
operand.gsub(/(#{mode})\w+(\+(\d+))?/, new_value)
|
469
|
+
end
|
470
|
+
|
471
|
+
def detect_opcode(mnemonic, operand)
|
472
|
+
SnesUtils.const_get(@cpu.capitalize)::Definitions::OPCODES_DATA.detect do |row|
|
473
|
+
mode = row[:mode]
|
474
|
+
regex = SnesUtils.const_get(@cpu.capitalize)::Definitions::MODES_REGEXES[mode]
|
475
|
+
row[:mnemonic] == mnemonic && regex =~ operand
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
def detect_operand(raw_operand)
|
480
|
+
SnesUtils.const_get(@cpu.capitalize)::Definitions::MODES_REGEXES[@mode].match(raw_operand)
|
481
|
+
end
|
482
|
+
|
483
|
+
def process_operand(operand_data)
|
484
|
+
if double_operand_instruction?
|
485
|
+
process_double_operand_instruction(operand_data)
|
486
|
+
else
|
487
|
+
operand = operand_data[1]&.to_i(16)
|
488
|
+
rel_instruction? ? process_rel_operand(operand) : little_endian(operand, @length - 1)
|
489
|
+
end
|
490
|
+
end
|
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
|
+
|
534
|
+
def process_double_operand_instruction(operand_data)
|
535
|
+
if bit_instruction?
|
536
|
+
process_bit_operand(operand_data)
|
537
|
+
else
|
538
|
+
operands = [operand_data[1], operand_data[2]].map { |o| o.to_i(16) }
|
539
|
+
operand_2 = rel_instruction? ? process_rel_operand(operands[1]) : operands[1]
|
540
|
+
|
541
|
+
rel_instruction? ? [operands[0], operand_2] : [operand_2, operands[0]]
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def process_bit_operand(operand_data)
|
546
|
+
m = operand_data[1].to_i(16)
|
547
|
+
raise "Out of range: m > 0x1fff: #{m}" if m > 0x1fff
|
548
|
+
|
549
|
+
b = operand_data[2].to_i(16)
|
550
|
+
raise "Out of range: b > 7: #{b}" if b > 7
|
551
|
+
|
552
|
+
little_endian(m << 3 | b, 2)
|
553
|
+
end
|
554
|
+
|
555
|
+
def process_rel_operand(operand)
|
556
|
+
relative_addr = operand - (@current_address & 0x00ffff) - @length
|
557
|
+
|
558
|
+
if @cpu == Vas::WDC65816 && @mode == :rell
|
559
|
+
raise "Relative address out of range: #{relative_addr}" if relative_addr < -32_768 || relative_addr > 32_767
|
560
|
+
|
561
|
+
relative_addr += 0x10000 if relative_addr < 0
|
562
|
+
little_endian(relative_addr, 2)
|
563
|
+
else
|
564
|
+
raise "Relative address out of range: #{relative_addr}" if relative_addr < -128 || relative_addr > 127
|
565
|
+
|
566
|
+
relative_addr += 0x100 if relative_addr < 0
|
567
|
+
relative_addr
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
def little_endian(operand, length)
|
572
|
+
if length > 2
|
573
|
+
[((operand >> 0) & 0xff), ((operand >> 8) & 0xff), ((operand >> 16) & 0xff)]
|
574
|
+
elsif length > 1
|
575
|
+
[((operand >> 0) & 0xff), ((operand >> 8) & 0xff)]
|
576
|
+
else
|
577
|
+
operand
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
def double_operand_instruction?
|
582
|
+
SnesUtils.const_get(@cpu.capitalize)::Definitions::DOUBLE_OPERAND_INSTRUCTIONS.include?(@mode)
|
583
|
+
end
|
584
|
+
|
585
|
+
def bit_instruction?
|
586
|
+
SnesUtils.const_get(@cpu.capitalize)::Definitions::BIT_INSTRUCTIONS.include?(@mode)
|
587
|
+
end
|
588
|
+
|
589
|
+
def rel_instruction?
|
590
|
+
SnesUtils.const_get(@cpu.capitalize)::Definitions::REL_INSTRUCTIONS.include?(@mode)
|
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
|
608
|
+
end
|
609
|
+
end
|