use anyhow::{Context, Result};
use std::io::{Cursor, Read};
#[derive(Debug, Clone)]
pub struct DolFile {
pub text_sections: Vec<Section>,
pub data_sections: Vec<Section>,
pub bss_address: u32,
pub bss_size: u32,
pub entry_point: u32,
pub path: String,
}
#[derive(Debug, Clone)]
pub struct Section {
pub offset: u32,
pub address: u32,
pub size: u32,
pub data: Vec<u8>,
pub executable: bool,
}
impl DolFile {
#[inline(never)] pub fn parse(data: &[u8], path: &str) -> Result<Self> {
const MIN_DOL_SIZE: usize = 0x100usize;
if data.len() < MIN_DOL_SIZE {
anyhow::bail!("DOL file too small: {} bytes (minimum {} bytes)", data.len(), MIN_DOL_SIZE);
}
let mut cursor: Cursor<&[u8]> = Cursor::new(data);
const NUM_TEXT_SECTIONS: usize = 7usize;
let mut text_offsets: [u32; NUM_TEXT_SECTIONS] = [0u32; NUM_TEXT_SECTIONS];
for offset in text_offsets.iter_mut() {
*offset = read_u32_be(&mut cursor)?;
}
const NUM_DATA_SECTIONS: usize = 11usize;
let mut data_offsets: [u32; NUM_DATA_SECTIONS] = [0u32; NUM_DATA_SECTIONS];
for offset in data_offsets.iter_mut() {
*offset = read_u32_be(&mut cursor)?;
}
let mut text_addresses: [u32; NUM_TEXT_SECTIONS] = [0u32; NUM_TEXT_SECTIONS];
for addr in text_addresses.iter_mut() {
*addr = read_u32_be(&mut cursor)?;
}
let mut data_addresses: [u32; NUM_DATA_SECTIONS] = [0u32; NUM_DATA_SECTIONS];
for addr in data_addresses.iter_mut() {
*addr = read_u32_be(&mut cursor)?;
}
let mut text_sizes: [u32; NUM_TEXT_SECTIONS] = [0u32; NUM_TEXT_SECTIONS];
for size in text_sizes.iter_mut() {
*size = read_u32_be(&mut cursor)?;
}
let mut data_sizes: [u32; NUM_DATA_SECTIONS] = [0u32; NUM_DATA_SECTIONS];
for size in data_sizes.iter_mut() {
*size = read_u32_be(&mut cursor)?;
}
const BSS_OFFSET: u64 = 0xD8u64;
cursor.set_position(BSS_OFFSET);
let bss_address: u32 = read_u32_be(&mut cursor)?;
let bss_size: u32 = read_u32_be(&mut cursor)?;
const ENTRY_POINT_OFFSET: u64 = 0xE0u64;
cursor.set_position(ENTRY_POINT_OFFSET);
let entry_point: u32 = read_u32_be(&mut cursor)?;
let mut text_sections: Vec<Section> = Vec::with_capacity(NUM_TEXT_SECTIONS);
for i in 0usize..NUM_TEXT_SECTIONS {
if text_offsets[i] != 0u32 && text_sizes[i] != 0u32 {
let offset: usize = text_offsets[i] as usize;
let size: usize = text_sizes[i] as usize;
if offset.wrapping_add(size) > data.len() {
anyhow::bail!("Text section {} extends beyond file: offset {}, size {}", i, offset, size);
}
let section_data: Vec<u8> = data[offset..offset.wrapping_add(size)].to_vec();
text_sections.push(Section {
offset: text_offsets[i],
address: text_addresses[i],
size: text_sizes[i],
data: section_data,
executable: true,
});
}
}
let mut data_sections: Vec<Section> = Vec::with_capacity(NUM_DATA_SECTIONS);
for i in 0usize..NUM_DATA_SECTIONS {
if data_offsets[i] != 0u32 && data_sizes[i] != 0u32 {
let offset: usize = data_offsets[i] as usize;
let size: usize = data_sizes[i] as usize;
if offset.wrapping_add(size) > data.len() {
anyhow::bail!("Data section {} extends beyond file: offset {}, size {}", i, offset, size);
}
let section_data: Vec<u8> = data[offset..offset.wrapping_add(size)].to_vec();
data_sections.push(Section {
offset: data_offsets[i],
address: data_addresses[i],
size: data_sizes[i],
data: section_data,
executable: false,
});
}
}
Ok(Self {
text_sections,
data_sections,
bss_address,
bss_size,
entry_point,
path: path.to_string(),
})
}
#[inline] pub fn get_all_sections(&self) -> Vec<Section> {
let mut all: Vec<Section> = Vec::with_capacity(self.text_sections.len() + self.data_sections.len());
all.extend_from_slice(&self.text_sections);
all.extend_from_slice(&self.data_sections);
all
}
}
#[inline] fn read_u32_be(cursor: &mut Cursor<&[u8]>) -> Result<u32> {
const U32_SIZE: usize = 4usize;
let mut buf: [u8; U32_SIZE] = [0u8; U32_SIZE];
cursor
.read_exact(&mut buf)
.context("Failed to read u32 from DOL file")?;
Ok(u32::from_be_bytes(buf))
}