Skip to main content

gcrecomp_core/recompiler/
parser.rs

1//! DOL File Parser
2//!
3//! This module provides parsing for GameCube DOL (Dolphin) executable files.
4//! The DOL format is the standard executable format for GameCube games.
5//!
6//! # DOL File Format
7//! The DOL file format consists of:
8//! - **Text sections**: Executable code sections (up to 7 sections)
9//! - **Data sections**: Data sections (up to 11 sections)
10//! - **BSS section**: Uninitialized data section (address and size)
11//! - **Entry point**: Program entry point address
12//!
13//! # Memory Optimizations
14//! - Uses const generics for fixed-size arrays (text/data section arrays)
15//! - Pre-allocates vectors with known capacity
16//! - Efficient byte reading with explicit buffer management
17
18use anyhow::{Context, Result};
19use std::io::{Cursor, Read};
20
21/// DOL file structure.
22///
23/// Represents a parsed GameCube DOL executable file with all sections loaded.
24#[derive(Debug, Clone)]
25pub struct DolFile {
26    /// Text (executable) sections
27    pub text_sections: Vec<Section>,
28    /// Data sections
29    pub data_sections: Vec<Section>,
30    /// BSS section address (uninitialized data)
31    pub bss_address: u32,
32    /// BSS section size
33    pub bss_size: u32,
34    /// Program entry point address
35    pub entry_point: u32,
36    /// File path (for reference)
37    pub path: String,
38}
39
40/// DOL file section.
41///
42/// Represents a single section (text or data) in a DOL file.
43#[derive(Debug, Clone)]
44pub struct Section {
45    /// Section offset in DOL file
46    pub offset: u32,
47    /// Section load address in memory
48    pub address: u32,
49    /// Section size in bytes
50    pub size: u32,
51    /// Section data
52    pub data: Vec<u8>,
53    /// Whether this section is executable (text section)
54    pub executable: bool,
55}
56
57impl DolFile {
58    /// Parse a DOL file from byte data.
59    ///
60    /// # Algorithm
61    /// 1. Read text section offsets, addresses, and sizes (7 sections)
62    /// 2. Read data section offsets, addresses, and sizes (11 sections)
63    /// 3. Read BSS address and size
64    /// 4. Read entry point
65    /// 5. Load section data from file
66    ///
67    /// # Arguments
68    /// * `data` - DOL file byte data
69    /// * `path` - File path (for reference)
70    ///
71    /// # Returns
72    /// `Result<DolFile>` - Parsed DOL file structure
73    ///
74    /// # Errors
75    /// Returns error if DOL file is malformed or too small
76    ///
77    /// # Examples
78    /// ```rust
79    /// let dol_data = std::fs::read("game.dol")?;
80    /// let dol_file = DolFile::parse(&dol_data, "game.dol")?;
81    /// ```
82    #[inline(never)] // Large function - don't inline
83    pub fn parse(data: &[u8], path: &str) -> Result<Self> {
84        const MIN_DOL_SIZE: usize = 0x100usize;
85        if data.len() < MIN_DOL_SIZE {
86            anyhow::bail!("DOL file too small: {} bytes (minimum {} bytes)", data.len(), MIN_DOL_SIZE);
87        }
88
89        let mut cursor: Cursor<&[u8]> = Cursor::new(data);
90
91        // Read text section offsets (7 sections, 4 bytes each)
92        const NUM_TEXT_SECTIONS: usize = 7usize;
93        let mut text_offsets: [u32; NUM_TEXT_SECTIONS] = [0u32; NUM_TEXT_SECTIONS];
94        for offset in text_offsets.iter_mut() {
95            *offset = read_u32_be(&mut cursor)?;
96        }
97
98        // Read data section offsets (11 sections, 4 bytes each)
99        const NUM_DATA_SECTIONS: usize = 11usize;
100        let mut data_offsets: [u32; NUM_DATA_SECTIONS] = [0u32; NUM_DATA_SECTIONS];
101        for offset in data_offsets.iter_mut() {
102            *offset = read_u32_be(&mut cursor)?;
103        }
104
105        // Read text section addresses (7 sections, 4 bytes each)
106        let mut text_addresses: [u32; NUM_TEXT_SECTIONS] = [0u32; NUM_TEXT_SECTIONS];
107        for addr in text_addresses.iter_mut() {
108            *addr = read_u32_be(&mut cursor)?;
109        }
110
111        // Read data section addresses (11 sections, 4 bytes each)
112        let mut data_addresses: [u32; NUM_DATA_SECTIONS] = [0u32; NUM_DATA_SECTIONS];
113        for addr in data_addresses.iter_mut() {
114            *addr = read_u32_be(&mut cursor)?;
115        }
116
117        // Read text section sizes (7 sections, 4 bytes each)
118        let mut text_sizes: [u32; NUM_TEXT_SECTIONS] = [0u32; NUM_TEXT_SECTIONS];
119        for size in text_sizes.iter_mut() {
120            *size = read_u32_be(&mut cursor)?;
121        }
122
123        // Read data section sizes (11 sections, 4 bytes each)
124        let mut data_sizes: [u32; NUM_DATA_SECTIONS] = [0u32; NUM_DATA_SECTIONS];
125        for size in data_sizes.iter_mut() {
126            *size = read_u32_be(&mut cursor)?;
127        }
128
129        // Read BSS address and size (at offset 0xD8)
130        const BSS_OFFSET: u64 = 0xD8u64;
131        cursor.set_position(BSS_OFFSET);
132        let bss_address: u32 = read_u32_be(&mut cursor)?;
133        let bss_size: u32 = read_u32_be(&mut cursor)?;
134
135        // Read entry point (at offset 0xE0)
136        const ENTRY_POINT_OFFSET: u64 = 0xE0u64;
137        cursor.set_position(ENTRY_POINT_OFFSET);
138        let entry_point: u32 = read_u32_be(&mut cursor)?;
139
140        // Parse text sections
141        let mut text_sections: Vec<Section> = Vec::with_capacity(NUM_TEXT_SECTIONS);
142        for i in 0usize..NUM_TEXT_SECTIONS {
143            if text_offsets[i] != 0u32 && text_sizes[i] != 0u32 {
144                let offset: usize = text_offsets[i] as usize;
145                let size: usize = text_sizes[i] as usize;
146                
147                if offset.wrapping_add(size) > data.len() {
148                    anyhow::bail!("Text section {} extends beyond file: offset {}, size {}", i, offset, size);
149                }
150
151                let section_data: Vec<u8> = data[offset..offset.wrapping_add(size)].to_vec();
152                text_sections.push(Section {
153                    offset: text_offsets[i],
154                    address: text_addresses[i],
155                    size: text_sizes[i],
156                    data: section_data,
157                    executable: true,
158                });
159            }
160        }
161
162        // Parse data sections
163        let mut data_sections: Vec<Section> = Vec::with_capacity(NUM_DATA_SECTIONS);
164        for i in 0usize..NUM_DATA_SECTIONS {
165            if data_offsets[i] != 0u32 && data_sizes[i] != 0u32 {
166                let offset: usize = data_offsets[i] as usize;
167                let size: usize = data_sizes[i] as usize;
168                
169                if offset.wrapping_add(size) > data.len() {
170                    anyhow::bail!("Data section {} extends beyond file: offset {}, size {}", i, offset, size);
171                }
172
173                let section_data: Vec<u8> = data[offset..offset.wrapping_add(size)].to_vec();
174                data_sections.push(Section {
175                    offset: data_offsets[i],
176                    address: data_addresses[i],
177                    size: data_sizes[i],
178                    data: section_data,
179                    executable: false,
180                });
181            }
182        }
183
184        Ok(Self {
185            text_sections,
186            data_sections,
187            bss_address,
188            bss_size,
189            entry_point,
190            path: path.to_string(),
191        })
192    }
193
194    /// Get all sections (text and data combined).
195    ///
196    /// # Returns
197    /// `Vec<Section>` - All sections from the DOL file
198    ///
199    /// # Examples
200    /// ```rust
201    /// let all_sections = dol_file.get_all_sections();
202    /// ```
203    #[inline] // Simple function - may be inlined
204    pub fn get_all_sections(&self) -> Vec<Section> {
205        let mut all: Vec<Section> = Vec::with_capacity(self.text_sections.len() + self.data_sections.len());
206        all.extend_from_slice(&self.text_sections);
207        all.extend_from_slice(&self.data_sections);
208        all
209    }
210}
211
212/// Read a big-endian u32 from a cursor.
213///
214/// # Arguments
215/// * `cursor` - Cursor to read from
216///
217/// # Returns
218/// `Result<u32>` - Read u32 value, or error if read fails
219#[inline] // Hot path - may be inlined
220fn read_u32_be(cursor: &mut Cursor<&[u8]>) -> Result<u32> {
221    const U32_SIZE: usize = 4usize;
222    let mut buf: [u8; U32_SIZE] = [0u8; U32_SIZE];
223    cursor
224        .read_exact(&mut buf)
225        .context("Failed to read u32 from DOL file")?;
226    Ok(u32::from_be_bytes(buf))
227}