2015-02-18 03:05:37 +00:00
|
|
|
|
|
|
|
module Assembler6502
|
|
|
|
|
|
|
|
####
|
2015-02-18 11:05:18 +00:00
|
|
|
## Let's simulate the entire 0xFFFF addressable memory space
|
|
|
|
## In the NES, and create reading and writing methods for it.
|
|
|
|
class MemorySpace
|
2015-02-22 13:59:03 +00:00
|
|
|
INESHeaderSize = 0x10
|
|
|
|
ProgROMSize = 0x4000
|
|
|
|
CharROMSize = 0x2000
|
2015-02-18 03:05:37 +00:00
|
|
|
|
|
|
|
####
|
2015-02-18 11:05:18 +00:00
|
|
|
## Create a completely zeroed memory space
|
|
|
|
def initialize(size = 2**16)
|
|
|
|
@memory = Array.new(size, 0x0)
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
####
|
2015-02-18 11:05:18 +00:00
|
|
|
## Read from memory
|
|
|
|
def read(address, count)
|
|
|
|
@memory[address..(address + count - 1)]
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
####
|
2015-02-18 11:05:18 +00:00
|
|
|
## Write to memory
|
|
|
|
def write(address, bytes)
|
|
|
|
bytes.each_with_index do |byte, index|
|
|
|
|
@memory[address + index] = byte
|
|
|
|
end
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
####
|
2015-02-18 11:05:18 +00:00
|
|
|
## Return the memory as an array of bytes to write to disk
|
|
|
|
def emit_bytes
|
|
|
|
@memory
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
|
2015-02-18 11:05:18 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
####
|
|
|
|
## The Main Assembler
|
|
|
|
class Assembler
|
2015-02-18 03:05:37 +00:00
|
|
|
|
2015-02-19 02:36:22 +00:00
|
|
|
## Custom exceptions
|
|
|
|
class INESHeaderNotFound < StandardError; end
|
|
|
|
|
|
|
|
|
2015-02-18 03:05:37 +00:00
|
|
|
####
|
2015-02-18 11:05:18 +00:00
|
|
|
## Assemble from a file to a file
|
|
|
|
def self.from_file(infile, outfile)
|
|
|
|
assembler = self.new(File.read(infile))
|
|
|
|
byte_array = assembler.assemble
|
|
|
|
|
|
|
|
File.open(outfile, 'w') do |fp|
|
|
|
|
fp.write(byte_array.pack('C*'))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
####
|
|
|
|
## Assemble 6502 Mnemomics and .directives into a program
|
|
|
|
def initialize(assembly_code)
|
|
|
|
@ines_header = nil
|
|
|
|
@assembly_code = assembly_code
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
####
|
2015-02-18 11:05:18 +00:00
|
|
|
## Run the assembly process into a virtual memory object
|
|
|
|
def assemble_in_virtual_memory
|
|
|
|
address = 0x0
|
|
|
|
labels = {}
|
|
|
|
memory = MemorySpace.new
|
|
|
|
unresolved_instructions = []
|
|
|
|
|
|
|
|
puts "Assembling, first pass..."
|
|
|
|
@assembly_code.split(/\n/).each do |raw_line|
|
|
|
|
sanitized = Assembler6502.sanitize_line(raw_line)
|
|
|
|
next if sanitized.empty?
|
|
|
|
parsed_line = Assembler6502::Instruction.parse(sanitized, address)
|
|
|
|
|
2015-02-18 03:05:37 +00:00
|
|
|
case parsed_line
|
2015-02-18 11:05:18 +00:00
|
|
|
when INESHeader
|
|
|
|
fail(SyntaxError, "Already got ines header") unless @ines_header.nil?
|
|
|
|
@ines_header = parsed_line
|
2015-02-19 02:36:22 +00:00
|
|
|
puts "\tGot iNES Header"
|
2015-02-18 11:05:18 +00:00
|
|
|
|
|
|
|
when Org
|
|
|
|
address = parsed_line.address
|
|
|
|
puts "\tMoving to address: $%X" % address
|
|
|
|
|
2015-02-18 03:05:37 +00:00
|
|
|
when Label
|
2015-02-18 11:05:18 +00:00
|
|
|
puts "\tLabel #{parsed_line.label} = $%X" % parsed_line.address
|
2015-02-18 03:05:37 +00:00
|
|
|
labels[parsed_line.label.to_sym] = parsed_line
|
2015-02-18 11:05:18 +00:00
|
|
|
|
2015-02-18 03:05:37 +00:00
|
|
|
when Instruction
|
2015-02-18 11:05:18 +00:00
|
|
|
if parsed_line.unresolved_symbols?
|
|
|
|
puts "\tSaving instruction with unresolved symbols #{parsed_line}, for second pass"
|
|
|
|
unresolved_instructions << parsed_line
|
|
|
|
else
|
2015-02-22 13:59:03 +00:00
|
|
|
puts "\tWriting instruction #{parsed_line}"
|
2015-02-18 11:05:18 +00:00
|
|
|
memory.write(parsed_line.address, parsed_line.emit_bytes)
|
|
|
|
end
|
2015-02-18 03:05:37 +00:00
|
|
|
address += parsed_line.length
|
2015-02-18 11:05:18 +00:00
|
|
|
|
|
|
|
when IncBin
|
2015-02-22 13:59:03 +00:00
|
|
|
puts "\t Including binary file #{parsed_line.filepath}"
|
|
|
|
memory.write(parsed_line.address, parsed_line.emit_bytes)
|
|
|
|
address += parsed_line.size
|
2015-02-18 11:05:18 +00:00
|
|
|
|
|
|
|
when DW
|
|
|
|
if parsed_line.unresolved_symbols?
|
2015-02-22 13:59:03 +00:00
|
|
|
puts "\tSaving #{parsed_line} directive with unresolved symbols, for second pass"
|
2015-02-18 11:05:18 +00:00
|
|
|
unresolved_instructions << parsed_line
|
|
|
|
else
|
2015-02-22 13:59:03 +00:00
|
|
|
puts "\tWriting #{parsed_line} to memory"
|
2015-02-18 11:05:18 +00:00
|
|
|
memory.write(address, parsed_line.emit_bytes)
|
|
|
|
end
|
|
|
|
address += 2
|
|
|
|
|
|
|
|
when Bytes
|
|
|
|
bytes = parsed_line.emit_bytes
|
2015-02-19 02:36:22 +00:00
|
|
|
puts "\tWriting raw #{bytes.size} bytes to #{sprintf("$%X", address)}"
|
|
|
|
memory.write(address, bytes)
|
|
|
|
address += bytes.size
|
|
|
|
|
|
|
|
when ASCII
|
|
|
|
bytes = parsed_line.emit_bytes
|
|
|
|
puts "\tWriting ascii string to memory \"#{bytes.pack('C*')}\""
|
2015-02-18 11:05:18 +00:00
|
|
|
memory.write(address, bytes)
|
|
|
|
address += bytes.size
|
2015-02-19 02:36:22 +00:00
|
|
|
|
2015-02-18 03:05:37 +00:00
|
|
|
else
|
2015-02-18 11:05:18 +00:00
|
|
|
fail(SyntaxError, sprintf("%.4X: Failed to parse: #{parsed_line}", address))
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-02-22 13:59:03 +00:00
|
|
|
puts "Second pass: Resolving Symbols..."
|
2015-02-18 11:05:18 +00:00
|
|
|
unresolved_instructions.each do |instruction|
|
2015-02-18 03:05:37 +00:00
|
|
|
if instruction.unresolved_symbols?
|
|
|
|
instruction.resolve_symbols(labels)
|
|
|
|
end
|
2015-02-22 13:59:03 +00:00
|
|
|
puts "\tResolved #{instruction}"
|
2015-02-18 11:05:18 +00:00
|
|
|
memory.write(instruction.address, instruction.emit_bytes)
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
2015-02-18 11:05:18 +00:00
|
|
|
puts 'Done'
|
|
|
|
|
|
|
|
memory
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
####
|
|
|
|
## After assembling the binary into the full 16-bit memory space
|
|
|
|
## we can now slice out the parts that should go into the binary ROM
|
|
|
|
## I am guessing the ROM size should be 1 bank of 16KB cartridge ROM
|
|
|
|
## plus the 16 byte iNES header. If the ROM is written into memory
|
|
|
|
## beginning at 0xC000, this should reach right up to the interrupt vectors
|
2015-02-22 14:33:36 +00:00
|
|
|
def assemble
|
2015-02-18 11:05:18 +00:00
|
|
|
virtual_memory = assemble_in_virtual_memory
|
2015-02-19 02:36:22 +00:00
|
|
|
|
|
|
|
## First we need to be sure we have an iNES header
|
|
|
|
fail(INESHeaderNotFound) if @ines_header.nil?
|
|
|
|
|
|
|
|
## Create memory to hold the ROM
|
|
|
|
nes_rom = MemorySpace.new(0x10 + 0x4000)
|
|
|
|
|
|
|
|
## First write the iNES header itself
|
|
|
|
nes_rom.write(0x0, @ines_header.emit_bytes)
|
|
|
|
|
|
|
|
## Write only one PROG section from 0xC000
|
|
|
|
start_address = 0xC000
|
|
|
|
length = 0x4000
|
|
|
|
prog_rom = virtual_memory.read(start_address, length)
|
|
|
|
write_start = 0x10
|
|
|
|
nes_rom.write(write_start, prog_rom)
|
|
|
|
|
|
|
|
## Now try writing one CHR-ROM section from 0x0000
|
|
|
|
start_address = 0x0000
|
|
|
|
length = 0x4000
|
|
|
|
char_rom = virtual_memory.read(start_address, length)
|
|
|
|
write_start = 0x10 + 0x4000
|
|
|
|
nes_rom.write(write_start, char_rom)
|
|
|
|
|
2015-02-18 11:05:18 +00:00
|
|
|
nes_rom.emit_bytes
|
2015-02-19 02:36:22 +00:00
|
|
|
|
|
|
|
#rom_size = 16 + (0xffff - 0xc000)
|
|
|
|
#nes_rom = MemorySpace.new(rom_size)
|
|
|
|
#nes_rom.write(0x0, virtual_memory.read(0x0, 0x10))
|
|
|
|
#nes_rom.write(0x10, virtual_memory.read(0xC000, 0x4000))
|
|
|
|
#nes_rom.emit_bytes
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
|
2015-02-22 13:59:03 +00:00
|
|
|
|
|
|
|
####
|
2015-02-23 01:39:35 +00:00
|
|
|
## This is all crap, I must research how banks and mappers work
|
|
|
|
#def assemble_new_crap
|
|
|
|
# virtual_memory = assemble_in_virtual_memory
|
|
|
|
|
|
|
|
# ## First we need to be sure we have an iNES header
|
|
|
|
# fail(INESHeaderNotFound) if @ines_header.nil?
|
|
|
|
|
|
|
|
# ## Now, we should decide how big the ROM image will be.
|
|
|
|
# ## And reserve memory build the image in
|
|
|
|
# nes_rom_size = MemorySpace::INESHeaderSize
|
|
|
|
# nes_rom_size += @ines_header.prog * MemorySpace::ProgROMSize
|
|
|
|
# nes_rom_size += @ines_header.char * MemorySpace::CharROMSize
|
|
|
|
# nes_rom = MemorySpace.new(nes_rom_size)
|
|
|
|
# puts "ROM will be #{nes_rom_size} bytes"
|
|
|
|
|
|
|
|
# ## Write the ines header to the ROM
|
|
|
|
# nes_rom.write(0x0, @ines_header.emit_bytes)
|
|
|
|
# puts "Wrote 16 byte ines header"
|
|
|
|
|
|
|
|
# ## If prog rom is >= 1 write the 16kb chunk from 0x8000
|
|
|
|
# if @ines_header.prog >= 1
|
|
|
|
# nes_rom.write(0x10, virtual_memory.read(0x8000, MemorySpace::ProgROMSize))
|
|
|
|
# puts "Wrote 16KB byte prog rom 1"
|
|
|
|
# end
|
|
|
|
|
|
|
|
# ## If prog rom is == 2 write the 16kb chunk from 0xC000
|
|
|
|
# #if @ines_header.prog == 2
|
|
|
|
# #nes_rom.write(0x10 + 0x4000, virtual_memory.read(0xC000, MemorySpace::ProgROMSize))
|
|
|
|
# #puts "Wrote 16KB byte prog rom 2"
|
|
|
|
# #end
|
2015-02-22 14:33:36 +00:00
|
|
|
#fail("Can only have 2 prog rom slots") if @ines_header.prog > 2
|
2015-02-22 13:59:03 +00:00
|
|
|
|
|
|
|
## If char rom is >= 1 write the 8kb chunk from 0x0000
|
2015-02-22 14:33:36 +00:00
|
|
|
#if @ines_header.char >= 1
|
|
|
|
#char_start = 0x10 + (@ines_header.prog * MemorySpace::ProgROMSize)
|
|
|
|
#nes_rom.write(char_start, virtual_memory.read(0x0000, MemorySpace::CharROMSize))
|
|
|
|
#puts "Wrote 8KB byte char rom 1"
|
|
|
|
#end
|
2015-02-22 13:59:03 +00:00
|
|
|
|
|
|
|
## If char rom is == 2 write the 8kb chunk from 0x2000
|
2015-02-22 14:33:36 +00:00
|
|
|
#if @ines_header.char == 2
|
|
|
|
#char_start = 0x10 + (@ines_header.prog * MemorySpace::ProgROMSize) + MemorySpace::CharROMSize
|
|
|
|
#nes_rom.write(char_start, virtual_memory.read(0x2000, MemorySpace::CharROMSize))
|
|
|
|
#puts "Wrote 8KB byte char rom 2"
|
|
|
|
#end
|
2015-02-22 13:59:03 +00:00
|
|
|
|
2015-02-23 01:39:35 +00:00
|
|
|
#nes_rom.emit_bytes
|
|
|
|
#end
|
2015-02-22 13:59:03 +00:00
|
|
|
|
2015-02-18 03:05:37 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|