diff --git a/Cargo.lock b/Cargo.lock index 313f209..cf6ced7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -65,6 +74,15 @@ dependencies = [ "syn", ] +[[package]] +name = "dol" +version = "0.1.0" +dependencies = [ + "bincode", + "serde", + "thiserror", +] + [[package]] name = "either" version = "1.6.1" @@ -478,6 +496,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1056a0db1978e9dbf0f6e4fca677f6f9143dc1c19de346f22cac23e422196834" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13af2fbb8b60a8950d6c72a56d2095c28870367cc8e10c55e9745bac4995a2c4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sfmt" version = "0.6.0" @@ -520,6 +558,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-width" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index c42daae..f70da3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "disasm", "disasm-py", + "dol", "macros", "fuzz", "flow-graph", diff --git a/dol/Cargo.toml b/dol/Cargo.toml new file mode 100644 index 0000000..195d6a4 --- /dev/null +++ b/dol/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dol" +version = "0.1.0" +edition = "2018" +authors = ["Richard Patel "] +license = "GPL-3.0-or-later" +description = "Deserializer for the DOL executable format" +repository = "https://github.com/terorie/ppc750cl" + +[dependencies] +bincode = "1.3" +serde = { version = "1.0", features = ["derive"] } +thiserror = "1.0" diff --git a/dol/src/lib.rs b/dol/src/lib.rs new file mode 100644 index 0000000..64677b7 --- /dev/null +++ b/dol/src/lib.rs @@ -0,0 +1,165 @@ +use std::io::{Read, Seek, SeekFrom}; + +use bincode::Options; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +/// A loaded DOL executable. +pub struct Dol { + pub header: DolHeader, + pub memory: Vec, + pub memory_offset: u32, +} + +/// An error that can be raised during DOL parsing. +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + BincodeError(bincode::Error), + #[error("{0}")] + IOError(std::io::Error), + #[error("No sections in DOL")] + NoSections, + #[error("Overlapping sections")] + OverlappingSections, + #[error("Section sizes overflow")] + SizeOverflow, +} + +impl From for Error { + fn from(e: bincode::Error) -> Self { + Self::BincodeError(e) + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Self::IOError(e) + } +} + +/// The result of a DOL parsing. +pub type Result = std::result::Result; + +impl Dol { + /// Reads a DOL executable from a `Reader`. + pub fn read_from(mut r: R) -> Result + where + R: Read + Seek, + { + // Read header. + let header = DolHeader::read_from(&mut r)?; + Dol::read_with_header(r, header) + } + + /// Reads a DOL body from a `Reader` given a header. + pub fn read_with_header(mut r: R, header: DolHeader) -> Result + where + R: Read + Seek, + { + let dol_start = r.stream_position()? - DolHeader::SERIALIZED_SIZE; + // Re-arrange section specifiers into a more workable format. + // Also remove the text vs data distinction because it's irrelevant. + struct DolSection { + offset: u32, + target: u32, + size: u32, + } + let mut dol_sections = + Vec::with_capacity(DolHeader::TEXT_SECTION_COUNT + DolHeader::DATA_SECTION_COUNT); + for i in 0..DolHeader::TEXT_SECTION_COUNT { + if header.text_sizes[i] > 0 { + dol_sections.push(DolSection { + offset: header.text_offsets[i], + target: header.text_targets[i], + size: header.text_sizes[i], + }); + } + } + for i in 0..DolHeader::DATA_SECTION_COUNT { + if header.data_sizes[i] > 0 { + dol_sections.push(DolSection { + offset: header.data_offsets[i], + target: header.data_targets[i], + size: header.data_sizes[i], + }); + } + } + if dol_sections.is_empty() { + return Err(Error::NoSections); + } + // Sort sections by target address to prepare them for mapping. + dol_sections.sort_by_key(|s| s.target); + // Verify that sections are not overlapping. + let mut end_target_addr = 0u32; + for section in &dol_sections { + if section.target < end_target_addr { + return Err(Error::OverlappingSections); + } + end_target_addr = section.target + section.size + } + // Remember the memory offset of the first section. + // Then shift all sections to the beginning of memory. + let memory_offset = dol_sections[0].target; + for mut section in &mut dol_sections { + section.target -= memory_offset; + } + // Get the size of all sections combined. + let mut total_size = 0usize; + for section in &dol_sections { + if let Some(sum) = total_size.checked_add(section.size as usize) { + total_size = sum; + } else { + return Err(Error::SizeOverflow); + } + } + // Create memory. + let mut memory = vec![0u8; total_size]; + // Read sections into memory. + for section in &dol_sections { + r.seek(SeekFrom::Start(dol_start + section.offset as u64))?; + let mem_start = section.target as usize; + let mem_end = mem_start + section.size as usize; + r.read_exact(&mut memory[mem_start..mem_end])?; + } + Ok(Self { + header, + memory, + memory_offset, + }) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct DolHeader { + text_offsets: [u32; Self::TEXT_SECTION_COUNT], + data_offsets: [u32; Self::DATA_SECTION_COUNT], + text_targets: [u32; Self::TEXT_SECTION_COUNT], + data_targets: [u32; Self::DATA_SECTION_COUNT], + text_sizes: [u32; Self::TEXT_SECTION_COUNT], + data_sizes: [u32; Self::DATA_SECTION_COUNT], + bss_target: u32, + bss_size: u32, + entry_point: u32, +} + +impl DolHeader { + const TEXT_SECTION_COUNT: usize = 7; + const DATA_SECTION_COUNT: usize = 11; + const SERIALIZED_SIZE: u64 = 0x100; + + fn bincode_options() -> impl bincode::Options { + bincode::DefaultOptions::new() + .with_big_endian() + .allow_trailing_bytes() + } + + /// Reads the DOL header from a `Reader`. + pub fn read_from(mut r: R) -> bincode::Result { + // Deserialize DOL header. + let this: Self = Self::bincode_options().deserialize_from(&mut r)?; + // Read padding. + let _: [u8; 0x1c] = Self::bincode_options().deserialize_from(&mut r)?; + Ok(this) + } +}