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