agb/agb-gbafix/src/main.rs

103 lines
2.9 KiB
Rust
Raw Normal View History

2023-04-07 08:16:38 +10:00
use anyhow::{anyhow, bail, ensure, Result};
2023-04-07 08:43:57 +10:00
use clap::{arg, command, value_parser};
2023-04-07 08:16:38 +10:00
2023-04-07 06:09:44 +10:00
use std::{
2023-04-07 08:16:38 +10:00
fs,
2023-04-07 06:54:26 +10:00
io::{BufWriter, Write},
2023-04-07 06:09:44 +10:00
path::PathBuf,
};
2023-04-07 08:16:38 +10:00
fn main() -> Result<()> {
2023-04-07 08:43:57 +10:00
let matches = command!()
.arg(arg!(<INPUT> "Input elf file").value_parser(value_parser!(PathBuf)))
.arg(arg!(-o --output <OUTPUT> "Set output file, defaults to replacing INPUT's extension to .gba").value_parser(value_parser!(PathBuf)))
.get_matches();
2023-04-07 08:13:42 +10:00
2023-04-07 08:43:57 +10:00
let input = matches.get_one::<PathBuf>("INPUT").unwrap();
let output = match matches.get_one::<PathBuf>("output") {
Some(output) => output.clone(),
None => input.with_extension("gba"),
};
let mut output = BufWriter::new(fs::File::create(output)?);
let file_data = fs::read(input)?;
2023-04-07 06:09:44 +10:00
2023-04-07 08:13:42 +10:00
write_gba_file(file_data.as_slice(), &mut output)?;
output.flush()?;
Ok(())
}
2023-04-07 08:16:38 +10:00
fn write_gba_file<W: Write>(input: &[u8], output: &mut W) -> Result<()> {
2023-04-07 08:13:42 +10:00
let elf_file = elf::ElfBytes::<elf::endian::AnyEndian>::minimal_parse(input)?;
2023-04-07 06:09:44 +10:00
2023-04-07 06:54:26 +10:00
let section_headers = elf_file
.section_headers()
2023-04-07 08:16:38 +10:00
.ok_or_else(|| anyhow!("Failed to parse as elf file"))?;
2023-04-07 06:09:44 +10:00
2023-04-07 06:54:26 +10:00
let mut header = gbafix::GBAHeader::default();
2023-04-07 06:09:44 +10:00
2023-04-07 08:09:19 +10:00
const GBA_START_ADDRESS: u64 = 0x8000000;
let mut address = GBA_START_ADDRESS;
2023-04-07 06:54:26 +10:00
for section_header in section_headers.iter() {
2023-04-07 06:09:44 +10:00
const SHT_NOBITS: u32 = 8;
const SHT_NULL: u32 = 0;
const SHF_ALLOC: u64 = 2;
if (section_header.sh_type == SHT_NOBITS || section_header.sh_type == SHT_NULL)
|| section_header.sh_flags & SHF_ALLOC == 0
{
continue;
}
2023-04-07 08:35:44 +10:00
if address < section_header.sh_addr {
for _ in address..section_header.sh_addr {
output.write_all(&[0])?;
}
address = section_header.sh_addr;
2023-04-07 08:09:19 +10:00
}
2023-04-07 06:54:26 +10:00
let (mut data, compression) = elf_file.section_data(&section_header)?;
2023-04-07 06:09:44 +10:00
if let Some(compression) = compression {
2023-04-07 08:16:38 +10:00
bail!("Cannot decompress elf content, but got compression header {compression:?}");
2023-04-07 06:09:44 +10:00
}
2023-04-07 08:09:19 +10:00
if address == GBA_START_ADDRESS {
2023-04-07 06:55:11 +10:00
const GBA_HEADER_SIZE: usize = 192;
2023-04-07 08:16:38 +10:00
ensure!(
2023-04-07 06:55:11 +10:00
data.len() > GBA_HEADER_SIZE,
2023-04-07 06:54:26 +10:00
"first section must be at least as big as the gba header"
);
header.start_code = data[0..4].try_into().unwrap();
header.update_checksum();
let header_bytes = bytemuck::bytes_of(&header);
output.write_all(header_bytes)?;
2023-04-07 06:55:11 +10:00
data = &data[GBA_HEADER_SIZE..];
2023-04-07 08:09:19 +10:00
address += GBA_HEADER_SIZE as u64;
2023-04-07 06:54:26 +10:00
}
output.write_all(data)?;
2023-04-07 08:09:19 +10:00
address += data.len() as u64;
2023-04-07 06:09:44 +10:00
}
2023-04-07 08:35:44 +10:00
let length = address - GBA_START_ADDRESS;
if !length.is_power_of_two() {
let required_padding = length.next_power_of_two() - length;
for _ in 0..required_padding {
output.write_all(&[0])?;
}
}
2023-04-07 06:09:44 +10:00
Ok(())
}