use std::env;
use std::fs::File;
use std::fs;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::collections::HashMap;
use anyhow::{Error, bail};
use image;
use crate::header::{Header, CartridgeType};
use crate::ast::{Instruction, ExprRunError, Expr};
use crate::constants::*;
use crate::parser;
use crate::audio;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
}
impl Color {
pub fn new(red: u8, green: u8, blue: u8) -> Color {
Color { red, green, blue }
}
}
enum Data {
Instructions (Vec<Instruction>),
Binary (Vec<u8>),
Header (Header),
DummyInterruptsAndJumps,
}
enum DataSource {
AsmFile (String),
AudioFile (String),
AudioPlayer,
Code
}
impl DataSource {
pub fn to_string(&self) -> String {
match self {
DataSource::Code => format!("data generated by rust code"),
DataSource::AudioPlayer => format!("instructions generated by the built-in ggbasm audio player"),
DataSource::AudioFile (name) => format!("instructions generated by audio file: {}", name),
DataSource::AsmFile (name) => format!("instructions generated by asm file {}", name)
}
}
}
struct DataHolder {
data: Data,
#[allow(dead_code)]
source: DataSource,
address: u32,
}
pub struct RomBuilder {
data: Vec<DataHolder>,
address: u32,
root_dir: PathBuf,
constants: HashMap<String, i64>,
}
impl RomBuilder {
pub fn new() -> Result<RomBuilder, Error> {
Ok(RomBuilder {
data: vec!(),
address: 0,
root_dir: RomBuilder::root_dir()?,
constants: HashMap::new(),
})
}
pub fn add_basic_interrupts_and_jumps(mut self) -> Result<Self, Error> {
if self.address != 0x0000 {
bail!("Attempted to add header data when address != 0x0000");
}
self.data.push(DataHolder {
data: Data::DummyInterruptsAndJumps,
address: 0,
source: DataSource::Code,
});
self.address = 0x104;
Ok(self)
}
pub fn add_header(mut self, header: Header) -> Result<Self, Error> {
if self.address != 0x0104 {
bail!("Attempted to add header data when address != 0x0104");
}
if header.title.as_bytes().len() > 0x10 {
bail!("Header title was larger than 16 bytes.");
}
if header.title.as_bytes().len() == 0x10 && header.color_support.is_supported() {
bail!("Header title was 16 bytes while supporting color.");
}
if header.licence.as_bytes().len() > 2 {
bail!("Header licence was larger than 2 bytes.");
}
self.data.push(DataHolder {
data: Data::Header (header),
address: self.address,
source: DataSource::Code,
});
self.address = 0x150;
Ok(self)
}
pub fn add_bytes(mut self, bytes: Vec<u8>, identifier: &str) -> Result<Self, Error> {
let len = bytes.len() as u32;
if let Some(_) = self.constants.insert(identifier.to_string(), self.address as i64) {
bail!("Identifier {} is already used", identifier)
}
self.data.push(DataHolder {
data: Data::Binary (bytes),
address: self.address,
source: DataSource::Code,
});
let prev_bank = self.get_bank();
self.address += len as u32;
if prev_bank == self.get_bank() {
Ok(self)
} else {
bail!("The added instructions cross bank boundaries.");
}
}
pub fn add_image(mut self, file_name: &str, identifier: &str, color_map: &HashMap<Color, u8>) -> Result<Self, Error> {
if let Some(_) = self.constants.insert(identifier.to_string(), self.address as i64) {
bail!("Identifier {} is already used", identifier)
}
let path = self.root_dir.as_path().join("graphics").join(file_name);
let image = match image::open(path) {
Ok(image) => image,
Err(err) => bail!("Cannot read file {} because: {}", file_name, err),
};
let mut bytes = vec!();
let image = image.to_rgb8();
for vert_tile in 0..(image.height() / 8) {
for hor_tile in 0..(image.width() / 8) {
for vert_line in 0..8 {
let mut byte0 = 0x00;
let mut byte1 = 0x00;
for hor_line in 0..8 {
let x = hor_tile * 8 + hor_line;
let y = vert_tile * 8 + vert_line;
let rgb = image.get_pixel(x, y);
let color = Color::new(rgb[0], rgb[1], rgb[2]);
if let Some(gb_color) = color_map.get(&color) {
byte0 |= (gb_color & 0b01) << (7 - hor_line);
byte1 |= ((gb_color & 0b10) >> 1) << (7 - hor_line);
}
else {
bail!("Color::new(0x{:x}, 0x{:x}, 0x{:x}) is not mapped to a gameboy color", color.red, color.green, color.blue);
}
}
bytes.push(byte0);
bytes.push(byte1);
}
}
}
let size = bytes.len();
self.data.push(DataHolder {
data: Data::Binary (bytes),
address: self.address,
source: DataSource::Code,
});
let prev_bank = self.get_bank();
self.address += size as u32;
if prev_bank == self.get_bank() {
Ok(self)
} else {
bail!("The added bytes cross bank boundaries.");
}
}
pub fn add_audio_file(self, file_name: &str) -> Result<Self, Error> {
let path = self.root_dir.as_path().join("audio").join(file_name);
let mut file = match File::open(path) {
Ok(file) => file,
Err(err) => bail!("Cannot read audio file {} because: {}", file_name, err),
};
let mut text = String::new();
file.read_to_string(&mut text)?;
let lines = match audio::parse_audio_text(&text) {
Ok(lines) => lines,
Err(err) => bail!("Cannot parse audio file {} because: {}", file_name, err),
};
let data = match audio::generate_audio_data(lines) {
Ok(lines) => lines,
Err(err) => bail!("Cannot generate audio from file {} because: {}", file_name, err),
};
self.add_instructions_inner(data, DataSource::AudioFile (file_name.to_string()))
}
pub fn add_audio_player(self) -> Result<Self, Error> {
let text = include_str!("audio_player.asm");
let instructions = parser::parse_asm(&text).unwrap()
.into_iter()
.enumerate()
.map(|(i, x)| x.expect(&format!("Invalid instruction on line {} of audio_player.asm", i + 1)))
.collect();
self.add_instructions_inner(instructions, DataSource::AudioPlayer)
}
pub fn add_asm_file(self, file_name: &str) -> Result<Self, Error> {
let path = self.root_dir.as_path().join("gbasm").join(file_name);
let mut file = match File::open(path) {
Ok(file) => file,
Err(err) => bail!("Cannot read asm file {} because: {}", file_name, err),
};
let mut text = String::new();
file.read_to_string(&mut text)?;
let option_instructions = match parser::parse_asm(&text) {
Ok(instructions) => instructions,
Err(err) => bail!("Cannot parse asm file {} because: {}", file_name, err),
};
let mut instructions = vec!();
for (i, instruction) in option_instructions.into_iter().enumerate() {
match instruction {
Some(instruction) => instructions.push(instruction),
None => {
bail!("Invalid instruction on line {} of {}", i + 1, file_name)
}
}
}
self.add_instructions_inner(instructions, DataSource::AsmFile (file_name.to_string()))
}
pub fn add_instructions(self, instructions: Vec<Instruction>) -> Result<Self, Error> {
self.add_instructions_inner(instructions, DataSource::Code)
}
fn add_instructions_inner(mut self, instructions: Vec<Instruction>, source: DataSource) -> Result<Self, Error> {
let mut cur_address = self.address;
for (i, instruction) in instructions.iter().enumerate() {
if let Instruction::Label (label) = instruction {
if let Some(_) = self.constants.insert(label.to_string(), cur_address as i64) {
bail!("Identifier {} is used twice: One usage occured in {} on line {}", label, source.to_string(), i + 1);
}
}
else {
cur_address += instruction.len((cur_address % ROM_BANK_SIZE) as u16) as u32;
}
}
self.data.push(DataHolder {
data: Data::Instructions(instructions),
address: self.address,
source,
});
let prev_bank = self.get_bank();
self.address = cur_address as u32;
if prev_bank == self.get_bank() {
Ok(self)
} else {
bail!("The added instructions cross bank boundaries.");
}
}
pub fn advance_address(mut self, rom_bank: u32, address: u32) -> Result<Self, Error> {
let new_address = address + rom_bank * ROM_BANK_SIZE;
if new_address >= self.address {
self.address = new_address;
Ok(self)
} else {
bail!("Attempted to advance to a previous address.")
}
}
pub fn get_address_global(&self) -> u32 {
self.address
}
pub fn get_address_bank(&self) -> u16 {
(self.address % ROM_BANK_SIZE) as u16
}
pub fn get_bank(&self) -> u32 {
self.address / ROM_BANK_SIZE
}
pub fn print_variables_by_value(self) -> Result<Self, Error> {
let mut sorted: Vec<_> = self.constants.iter().collect();
sorted.sort_by_key(|x| x.1);
for (ident, value) in sorted {
println!("0x{:x} - {}", value, ident);
}
Ok(self)
}
pub fn print_variables_by_identifier(self) -> Result<Self, Error> {
let mut sorted: Vec<_> = self.constants.iter().collect();
sorted.sort_by_key(|x| x.0.to_lowercase());
for (ident, value) in sorted {
println!("{} - 0x{:x}", ident, value);
}
Ok(self)
}
pub fn compile(mut self) -> Result<Vec<u8>, Error> {
if self.data.last().is_none() {
bail!("No instructions or binary data was added to the RomBuilder");
}
let rom_size_factor = if self.address <= ROM_BANK_SIZE * 2 {
0
} else if self.address <= ROM_BANK_SIZE * 4 {
1
} else if self.address <= ROM_BANK_SIZE * 8 {
2
} else if self.address <= ROM_BANK_SIZE * 16 {
3
} else if self.address <= ROM_BANK_SIZE * 32 {
4
} else if self.address <= ROM_BANK_SIZE * 64 {
5
} else if self.address <= ROM_BANK_SIZE * 128 {
6
} else if self.address <= ROM_BANK_SIZE * 256 {
7
} else if self.address <= ROM_BANK_SIZE * 512 {
8
} else {
bail!("ROM is too big, there is no MBC that supports a ROM size larger than 8MB, raw ROM size was {}", self.address);
};
let mut rom = vec!();
#[derive(Clone)]
struct EquHolder<'a> {
pub ident: &'a String,
pub expr: &'a Expr,
pub source: &'a DataSource,
pub line: u64,
}
let mut equs = vec!();
for data in &self.data {
match &data.data {
Data::DummyInterruptsAndJumps => { }
Data::Header (_) => { }
Data::Binary { .. } => { }
Data::Instructions (instructions) => {
for (i, instruction) in instructions.iter().enumerate() {
if let Instruction::Equ (ident, expr) = instruction {
equs.push(EquHolder {
expr,
ident,
source: &data.source,
line: i as u64 + 1,
});
}
}
}
}
}
let constants = &mut self.constants;
while !equs.is_empty() {
let prev_size = equs.len();
let mut outer_error = None;
let mut missing_idents = vec!();
equs.retain(|equ| {
match equ.expr.run(constants) {
Ok(value) => {
if let Some(_) = constants.insert(equ.ident.clone(), value) {
outer_error = Some(format!("Identifier {} is declared twice: One usage occured in {} on line {}", &equ.ident, equ.source.to_string(), equ.line));
}
false
}
Err(ExprRunError::MissingIdentifier (ident)) => {
missing_idents.push((equ.clone(), ident));
true
}
Err(ExprRunError::ResultDoesntFit (error)) |
Err(ExprRunError::ArithmeticError (error)) => {
outer_error = Some(format!("Error occured in {} on line {}: {}", equ.source.to_string(), equ.line, error));
true
}
}
});
if let Some(error) = outer_error {
bail!(error);
}
for (missing_ident_equ, missing_ident) in missing_idents {
let mut found_ident = false;
for search_equ in &equs {
if &missing_ident == search_equ.ident {
found_ident = true;
break
}
}
if !found_ident {
bail!(format!("Identifier {} is used in {} on line {} but is never declared.", missing_ident, missing_ident_equ.source.to_string(), missing_ident_equ.line));
}
}
if prev_size == equs.len() {
let mut fail_string = String::from("Cannot resolve constants, there is an infinite loop involving the following identifiers:\n");
for equ in equs {
fail_string.push_str(&format!("* {}\n", equ.ident));
}
bail!(fail_string);
}
}
for data in &self.data {
for _ in 0..data.address as i32 - rom.len() as i32 {
rom.push(0x00);
}
match &data.data {
Data::DummyInterruptsAndJumps => {
for _ in 0..8 {
rom.push(0xc3);
rom.push(0x00);
rom.push(0x01);
rom.push(0x00);
rom.push(0x00);
rom.push(0x00);
rom.push(0x00);
rom.push(0x00);
}
for _ in 0..5 {
rom.push(0xd9);
rom.push(0x00);
rom.push(0x00);
rom.push(0x00);
rom.push(0x00);
rom.push(0x00);
rom.push(0x00);
rom.push(0x00);
}
for _ in 0..0x98 {
rom.push(0x00);
}
rom.push(0x00);
rom.push(0xc3);
rom.push(0x50);
rom.push(0x01);
}
Data::Header (header) => {
header.write(&mut rom, rom_size_factor as u8);
}
Data::Binary (bytes) => {
rom.extend(bytes);
}
Data::Instructions (instructions) => {
for (i, instruction) in instructions.iter().enumerate() {
if let Err(err) = instruction.write_to_rom(&mut rom, &self.constants) {
bail!("Error occured in {} on line {}: {}", data.source.to_string(), i + 1, err);
}
}
}
}
}
if rom.len() < 0x14F {
bail!("ROM is too small, header is not finished. ROM was only {} bytes", rom.len());
}
let cartridge_type = CartridgeType::variant(rom[0x0147]);
let final_size_factor = rom[0x0148];
if final_size_factor >= 0x20 {
bail!("ROM size factor (0x0148) is too big, needs to be less than 32 was {}", final_size_factor);
}
let final_size = (ROM_BANK_SIZE * 2) << final_size_factor;
match cartridge_type {
CartridgeType::RomOnly | CartridgeType::RomRam | CartridgeType::RomRamBattery => {
if final_size_factor != 0 {
bail!("ROM is too big, there is no MBC so ROM size must be <= 32KB, was actually {}", final_size);
}
}
CartridgeType::Mbc1 | CartridgeType::Mbc1Ram |CartridgeType::Mbc1RamBattery => {
if final_size_factor > 6 {
bail!("ROM is too big, using MBC1 so ROM size must be <= 2MB, was actually {}", final_size);
}
}
CartridgeType::Mbc2 | CartridgeType::Mbc2Battery => {
if final_size_factor > 3 {
bail!("ROM is too big, using MBC2 so ROM size must be <= 256KB, was actually {}", final_size);
}
}
CartridgeType::Mmm01 | CartridgeType::Mmm01Ram | CartridgeType::Mmm01RamBattery => {
}
CartridgeType::Mbc3TimerBattery | CartridgeType::Mbc3TimerRamBattery | CartridgeType::Mbc3 |
CartridgeType::Mbc3Ram | CartridgeType::Mbc3RamBattery => {
if final_size_factor > 6 {
bail!("ROM is too big, using MBC3 so ROM size must be <= 2MB, was actually {}", final_size);
}
}
CartridgeType::Mbc5 | CartridgeType::Mbc5Ram | CartridgeType::Mbc5RamBattery |
CartridgeType::Mbc5Rumble | CartridgeType::Mbc5RumbleRam | CartridgeType::Mbc5RumbleRamBattery => {
if final_size_factor > 8 {
bail!("ROM is too big, using MBC5 so ROM size must be <= 8MB, was actually {}", final_size);
}
}
CartridgeType::PocketCamera => {
if final_size_factor > 8 {
bail!("ROM is too big, using PocketCamera so ROM size must be <= 1MB, was actually {}", final_size);
}
}
CartridgeType::HuC3 => {
}
CartridgeType::HuC1RamBattery => {
if final_size_factor > 6 {
bail!("ROM is too big, using HuC1 so ROM size must be <= 2MB, was actually {}", final_size);
}
}
CartridgeType::Unknown (_) => {
}
}
for _ in 0..final_size-rom.len() as u32 {
rom.push(0x00);
}
Ok(rom)
}
pub fn write_to_disk(self, name: &str) -> Result<(), Error> {
let output = self.root_dir.as_path().join(name);
let rom = self.compile()?;
File::create(&output)?.write(&rom)?;
Ok(())
}
pub fn write_to_disk_html(self, _name: &str) -> Result<(), Error> {
unimplemented!();
}
fn root_dir() -> Result<PathBuf, Error> {
let current_dir = env::current_dir()?;
let mut current = current_dir.as_path();
loop {
let toml = current.join("Cargo.toml");
if fs::metadata(&toml).is_ok() {
return Ok(toml.parent().unwrap().to_path_buf())
}
match current.parent() {
Some(p) => current = p,
None => bail!("Cant find a Cargo.toml in any of the parent directories")
}
}
}
}