1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use std::{
    fmt::Display,
    mem::{align_of, size_of},
};

use bytemuck::{Pod, PodCastError, Zeroable};
use snafu::{Backtrace, Snafu};

use super::{RawHeaderError, NITROCODE};

/// Build info for the ARM9 module. This is the raw version, see the plain one [here](super::super::BuildInfo).
#[repr(C)]
#[derive(Clone, Copy, Zeroable, Pod)]
pub struct BuildInfo {
    /// Offset to the start of [`super::AutoloadInfo`]s.
    pub autoload_infos_start: u32,
    /// Offset to the end of [`super::AutoloadInfo`]s.
    pub autoload_infos_end: u32,
    /// Offset to where the autoload blocks start.
    pub autoload_blocks: u32,
    /// Offset to the start of uninitialized data in this module.
    pub bss_start: u32,
    /// Offset to the end of uninitialized data in this module.
    pub bss_end: u32,
    /// Size of this module after compression.
    pub compressed_code_end: u32,
    /// SDK version? Value is higher for newer games, but it's unclear what this value is for.
    pub sdk_version: u32,
    nitrocode: u32,
    nitrocode_rev: u32,
}

/// Errors related to [`BuildInfo`].
#[derive(Debug, Snafu)]
pub enum RawBuildInfoError {
    /// See [`RawHeaderError`].
    #[snafu(transparent)]
    RawHeader {
        /// Source error.
        source: RawHeaderError,
    },
    /// Occurs when the input is too small to fit [`BuildInfo`].
    #[snafu(display("expected {expected:#x} bytes for build info but had only {actual:#x}:\n{backtrace}"))]
    DataTooSmall {
        /// Expected size.
        expected: usize,
        /// Actual input size.
        actual: usize,
        /// Backtrace to the source of the error.
        backtrace: Backtrace,
    },
    /// Occurs when the input is less aligned than [`BuildInfo`].
    #[snafu(display("expected {expected}-alignment for build info but got {actual}-alignment:\n{backtrace}"))]
    Misaligned {
        /// Expected alignment.
        expected: usize,
        /// Actual input alignment.
        actual: usize,
        /// Backtrace to the source of the error.
        backtrace: Backtrace,
    },
    /// Occurs when the input does not contain the nitrocode.
    #[snafu(display("expected nitrocode {expected:#x} at the end of build info but got {actual:#x}:\n{backtrace}"))]
    NoNitrocode {
        /// Expected value.
        expected: u32,
        /// Actual value.
        actual: u32,
        /// Backtrace to the source of the error.
        backtrace: Backtrace,
    },
}

impl BuildInfo {
    fn check_size(data: &'_ [u8]) -> Result<(), RawBuildInfoError> {
        let size = size_of::<Self>();
        if data.len() < size {
            DataTooSmallSnafu { expected: size, actual: data.len() }.fail()
        } else {
            Ok(())
        }
    }

    fn handle_pod_cast<T>(result: Result<T, PodCastError>, addr: usize) -> Result<T, RawBuildInfoError> {
        match result {
            Ok(build_info) => Ok(build_info),
            Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) => {
                MisalignedSnafu { expected: align_of::<Self>(), actual: 1usize << addr.trailing_zeros() }.fail()
            }
            Err(PodCastError::AlignmentMismatch) => panic!(),
            Err(PodCastError::OutputSliceWouldHaveSlop) => panic!(),
            Err(PodCastError::SizeMismatch) => unreachable!(),
        }
    }

    fn check_nitrocode(&self) -> Result<(), RawBuildInfoError> {
        if self.nitrocode != NITROCODE {
            NoNitrocodeSnafu { expected: NITROCODE, actual: self.nitrocode }.fail()
        } else if self.nitrocode_rev != NITROCODE.swap_bytes() {
            NoNitrocodeSnafu { expected: NITROCODE.swap_bytes(), actual: self.nitrocode_rev }.fail()
        } else {
            Ok(())
        }
    }

    /// Reinterprets a `&[u8]` as a reference to [`BuildInfo`].
    ///
    /// # Errors
    ///
    /// This function will return an error if the input is too small, not aligned enough or doesn't contain the nitrocode.
    pub fn borrow_from_slice(data: &'_ [u8]) -> Result<&'_ Self, RawBuildInfoError> {
        let size = size_of::<Self>();
        Self::check_size(data)?;
        let addr = data as *const [u8] as *const () as usize;
        let build_info: &Self = Self::handle_pod_cast(bytemuck::try_from_bytes(&data[..size]), addr)?;
        build_info.check_nitrocode()?;
        Ok(build_info)
    }

    /// Reinterprets a `&mut [u8]` as a mutable reference to [`BuildInfo`].
    ///
    /// # Errors
    ///
    /// This function will return an error if the input is too small, not aligned enough or doesn't contain the nitrocode.
    pub fn borrow_from_slice_mut(data: &'_ mut [u8]) -> Result<&'_ mut Self, RawBuildInfoError> {
        let size = size_of::<Self>();
        Self::check_size(data)?;
        let addr = data as *const [u8] as *const () as usize;
        let build_info: &mut Self = Self::handle_pod_cast(bytemuck::try_from_bytes_mut(&mut data[..size]), addr)?;
        build_info.check_nitrocode()?;
        Ok(build_info)
    }

    /// Returns whether this [`BuildInfo`] is compressed.
    pub fn is_compressed(&self) -> bool {
        self.compressed_code_end != 0
    }

    /// Creates a [`DisplayBuildInfo`] which implements [`Display`].
    pub fn display(&self, indent: usize) -> DisplayBuildInfo {
        DisplayBuildInfo { build_info: self, indent }
    }
}

/// Can be used to display values inside [`BuildInfo`].
pub struct DisplayBuildInfo<'a> {
    build_info: &'a BuildInfo,
    indent: usize,
}

impl<'a> Display for DisplayBuildInfo<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let i = format!("{:indent$}", "", indent = self.indent);
        let build_info = &self.build_info;
        writeln!(f, "{i}Autoload infos start .. : {:#x}", build_info.autoload_infos_start)?;
        writeln!(f, "{i}Autoload infos end .... : {:#x}", build_info.autoload_infos_end)?;
        writeln!(f, "{i}Autoload blocks ....... : {:#x}", build_info.autoload_blocks)?;
        writeln!(f, "{i}.bss start ............ : {:#x}", build_info.bss_start)?;
        writeln!(f, "{i}.bss end .............. : {:#x}", build_info.bss_end)?;
        writeln!(f, "{i}Compressed code end ... : {:#x}", build_info.compressed_code_end)?;
        writeln!(f, "{i}SDK version ........... : {:#x}", build_info.sdk_version)?;
        Ok(())
    }
}