ds-decomp 0.11.0

Library for ds-decomp, a DS decompilation toolkit.
Documentation
use std::backtrace::Backtrace;

use ds_rom::rom::{Arm9, raw::RawBuildInfoError};
use snafu::Snafu;
use unarm::args::{Argument, OffsetImm, Reg, Register};

use super::functions::{Function, FunctionAnalysisError, FunctionParseOptions, ParseFunctionError};
use crate::analysis::functions::IntoFunctionError;

#[derive(Clone, Copy)]
pub struct MainFunction {
    pub address: u32,
}

#[derive(Debug, Snafu)]
pub enum MainFunctionError {
    #[snafu(transparent)]
    RawBuildInfo { source: RawBuildInfoError },
    #[snafu(transparent)]
    FunctionAnalysis { source: FunctionAnalysisError },
    #[snafu(display("failed to analyze entrypoint function: {parse_result:x?}:\n{backtrace}"))]
    MainAnalysisFailed { parse_result: ParseFunctionError, backtrace: Backtrace },
    #[snafu(display("Expected entry function to contain pool constants:\n{backtrace}"))]
    NoPoolConstants { backtrace: Backtrace },
    #[snafu(display("Expected last instruction of entry function to be 'bx <reg>':\n{backtrace}"))]
    UnexpectedReturn { backtrace: Backtrace },
    #[snafu(display("No tail call found in entry function:\n{backtrace}"))]
    NoTailCall { backtrace: Backtrace },
}

impl MainFunction {
    fn find_tail_call(
        function: Function,
        module_code: &[u8],
        base_address: u32,
    ) -> Result<u32, MainFunctionError> {
        let mut parser = function.parser(module_code, base_address);

        let ins_size = parser.mode.instruction_size(0) as u32;
        let last_ins_addr =
            function.pool_constants().first().ok_or_else(|| NoPoolConstantsSnafu.build())?
                - ins_size;

        parser.seek_forward(last_ins_addr);
        let (_, _, last_ins) = parser.next().unwrap();

        let tail_call_reg = match (last_ins.mnemonic, last_ins.args[0], last_ins.args[1]) {
            ("bx", Argument::Reg(Reg { reg, .. }), Argument::None) => reg,
            _ => return UnexpectedReturnSnafu.fail(),
        };

        let mut p_tail_call = None;
        for (address, _ins, parsed_ins) in function.parser(module_code, base_address) {
            if function.pool_constants().contains(&address) {
                break;
            }
            let args = &parsed_ins.args;
            p_tail_call = match (parsed_ins.mnemonic, args[0], args[1], args[2], args[3]) {
                (
                    "ldr",
                    Argument::Reg(Reg { reg, .. }),
                    Argument::Reg(Reg { reg: pc, deref: true, .. }),
                    Argument::OffsetImm(OffsetImm { post_indexed: false, value: offset }),
                    Argument::None,
                ) if reg == tail_call_reg && pc == Register::Pc => Some(
                    ((address as i32 + offset) & !3) as u32
                        + if function.is_thumb() { 4 } else { 8 },
                ),
                _ => continue,
            };
        }
        let p_tail_call = p_tail_call.ok_or_else(|| NoTailCallSnafu.build())?;

        let function_code = function.code(module_code, base_address);
        let tail_call_data = &function_code[(p_tail_call - function.start_address()) as usize..];
        let tail_call = u32::from_le_bytes([
            tail_call_data[0],
            tail_call_data[1],
            tail_call_data[2],
            tail_call_data[3],
        ]);
        Ok(tail_call & !1)
    }

    pub fn find_in_arm9(arm9: &Arm9) -> Result<Self, MainFunctionError> {
        let code = arm9.code()?;

        let entry_addr = arm9.entry_function();
        let entry_code = &code[(entry_addr - arm9.base_address()) as usize..];
        let parse_result = Function::parse_function(FunctionParseOptions {
            name: "entry".to_string(),
            start_address: arm9.entry_function(),
            base_address: entry_addr,
            module_code: entry_code,
            known_end_address: None,
            module_start_address: arm9.base_address(),
            module_end_address: arm9.end_address()?,
            parse_options: Default::default(),
            ..Default::default()
        });
        let entry_func = match parse_result {
            Ok(function) => function,
            Err(FunctionAnalysisError::IntoFunction {
                source: IntoFunctionError::ParseFunction { source },
            }) => {
                return MainAnalysisFailedSnafu { parse_result: source }.fail();
            }
            Err(e) => return Err(e.into()),
        };

        let main = Self::find_tail_call(entry_func, entry_code, entry_addr)?;
        Ok(Self { address: main })
    }
}