class SnesUtils::Vas
Constants
- DIRECTIVE
- LABEL_OPERATORS
- SPC700
- WDC65816
Public Class Methods
hex(num, rjust_len = 2)
click to toggle source
# File lib/vas/vas.rb, line 145 def self.hex(num, rjust_len = 2) (num || 0).to_s(16).rjust(rjust_len, '0').upcase end
new(filename)
click to toggle source
# File lib/vas/vas.rb, line 14 def initialize(filename) raise "File not found: #{filename}" unless File.file?(filename) @filename = filename @file = {} @label_registry = [] @define_registry = {} @incbin_list = [] @byte_sequence_list = [] @memory = [] end
Public Instance Methods
assemble()
click to toggle source
# File lib/vas/vas.rb, line 26 def assemble construct_file 2.times do |pass| @program_counter = 0 @origin = 0 @base = 0 @cpu = WDC65816 assemble_file(pass) end write_label_registry insert_bytes incbin write end
assemble_file(pass)
click to toggle source
# File lib/vas/vas.rb, line 88 def assemble_file(pass) @file.each do |key, val| @line = val[:line] if @line.include?(':') arr = @line.split(':') label = arr[0].strip.chomp unless /^\w+$/ =~ label raise "Invalid label: #{label}" end register_label(label) if pass == 0 next unless arr[1] instruction = arr[1].strip.chomp else instruction = @line end next if instruction.empty? if instruction.start_with?(*DIRECTIVE) process_directive(instruction, pass) next end begin bytes = LineAssembler.new(instruction, **options).assemble rescue => e puts "Error at line #{val[:filename]}##{val[:line_no]} - (#{val[:orig_line]}) : #{e}" exit(1) end insert(bytes) if pass == 1 @program_counter += bytes.size end end
construct_file(filename = @filename)
click to toggle source
# File lib/vas/vas.rb, line 44 def construct_file(filename = @filename) File.open(filename).each_with_index do |raw_line, line_no| line = raw_line.split(';').first.strip.chomp next if line.empty? if line.start_with?('.include') directive = line.split(' ') inc_filename = directive[1].to_s.strip.chomp construct_file(inc_filename) elsif line.start_with?('.define') args = line.split(' ') key = "#{args[1]}" val = args[2..-1].join(' ').split(';').first.strip.chomp raise "Already defined: #{key}" unless @define_registry[key].nil? @define_registry[key] = val else new_line = replace_define(line) @file[SecureRandom.uuid] = { line: new_line, orig_line: line, line_no: line_no + 1, filename: filename } end end end
define_bytes(raw_bytes, pass)
click to toggle source
# File lib/vas/vas.rb, line 213 def define_bytes(raw_bytes, pass) bytes = raw_bytes.split(',').map { |rb| rb.scan(/.{2}/).reverse }.flatten.map do |b| bv = b.to_i(16) raise "Invalid byte: #{b} : #{@line}" if bv < 0 || bv > 0xff bv end @byte_sequence_list << [bytes, insert_index] if pass == 0 bytes.size end
incbin()
click to toggle source
# File lib/vas/vas.rb, line 204 def incbin @incbin_list.each do |filename, index| file = File.open(filename) bytes = file.each_byte.to_a @line = filename insert(bytes, index) end end
insert(bytes, insert_at = insert_index)
click to toggle source
# File lib/vas/vas.rb, line 129 def insert(bytes, insert_at = insert_index) @memory[insert_at..insert_at + bytes.size - 1] = bytes end
insert_bytes()
click to toggle source
# File lib/vas/vas.rb, line 224 def insert_bytes @byte_sequence_list.each do |bytes, index| insert(bytes, index) end end
insert_index()
click to toggle source
# File lib/vas/vas.rb, line 133 def insert_index @program_counter + @base end
options()
click to toggle source
# File lib/vas/vas.rb, line 149 def options { program_counter: @program_counter, origin: @origin, cpu: @cpu, label_registry: @label_registry } end
prepare_incbin(filename, pass)
click to toggle source
# File lib/vas/vas.rb, line 197 def prepare_incbin(filename, pass) raise "Incbin: file not found: #{filename}" unless File.file?(filename) @incbin_list << [filename, insert_index] if pass == 0 File.size(filename) || 0 end
process_directive(instruction, pass)
click to toggle source
# File lib/vas/vas.rb, line 158 def process_directive(instruction, pass) directive = instruction.split(' ') case directive[0] when '.65816' @cpu = WDC65816 when '.spc700' @cpu = SPC700 when '.org' update_origin(directive[1].to_i(16)) when '.base' @base = directive[1].to_i(16) when '.incbin' @program_counter += prepare_incbin(directive[1].to_s.strip.chomp, pass) when '.db' raw_line = directive[1..-1].join.to_s.strip.chomp line = LineAssembler.new(raw_line, **options).replace_labels(raw_line) @program_counter += define_bytes(line, pass) when '.rb' @program_counter += directive[1].to_i(16) when '.define' # TODO end end
register_label(label)
click to toggle source
# File lib/vas/vas.rb, line 124 def register_label(label) raise "Label already defined: #{label}" if @label_registry.detect { |l| l[0] == label } @label_registry << [label, @program_counter + @origin] end
replace_define(line)
click to toggle source
# File lib/vas/vas.rb, line 70 def replace_define(line) found = nil @define_registry.keys.each do |key| if line.match(/\b#{key}\b/) found = key break end end return line if found.nil? val = @define_registry[found] line.gsub(/\b#{found}/, val) end
update_base_from_origin()
click to toggle source
# File lib/vas/vas.rb, line 191 def update_base_from_origin # TODO: automatically update base # lorom/hirom scheme # spc700 scheme end
update_origin(param)
click to toggle source
# File lib/vas/vas.rb, line 184 def update_origin(param) @origin = param @program_counter = 0 update_base_from_origin end
write(filename = 'out.smc')
click to toggle source
# File lib/vas/vas.rb, line 137 def write(filename = 'out.smc') File.open(filename, 'w+b') do |file| file.write([@memory.map { |i| Vas::hex(i) }.join].pack('H*')) end filename end
write_label_registry()
click to toggle source
# File lib/vas/vas.rb, line 230 def write_label_registry longest = @label_registry.map{|r| r[0] }.max_by(&:length) File.open('labels.txt', 'w+b') do |file| @label_registry.each do |label| adjusted_label = label[0].ljust(longest.length, ' ') raw_address = Vas::hex(label[1], 6) address = "#{raw_address[0..1]}/#{raw_address[2..-1]}" file.write "#{adjusted_label} #{address}\n" end end end