Skip to main content

ds_rom/rom/raw/
build_info.rs

1use std::{
2    fmt::Display,
3    mem::{align_of, size_of},
4};
5
6use bytemuck::{Pod, PodCastError, Zeroable};
7use snafu::{Backtrace, Snafu};
8
9use super::{RawHeaderError, NITROCODE};
10
11/// Build info for the ARM9 module. This is the raw version, see the plain one [here](super::super::BuildInfo).
12#[repr(C)]
13#[derive(Clone, Copy, Zeroable, Pod)]
14pub struct BuildInfo {
15    /// Offset to the start of [`super::AutoloadInfo`]s.
16    pub autoload_infos_start: u32,
17    /// Offset to the end of [`super::AutoloadInfo`]s.
18    pub autoload_infos_end: u32,
19    /// Offset to where the autoload blocks start.
20    pub autoload_blocks: u32,
21    /// Offset to the start of uninitialized data in this module.
22    pub bss_start: u32,
23    /// Offset to the end of uninitialized data in this module.
24    pub bss_end: u32,
25    /// Size of this module after compression.
26    pub compressed_code_end: u32,
27    /// SDK version? Value is higher for newer games, but it's unclear what this value is for.
28    pub sdk_version: u32,
29    nitrocode: u32,
30    nitrocode_rev: u32,
31}
32
33/// Errors related to [`BuildInfo`].
34#[derive(Debug, Snafu)]
35pub enum RawBuildInfoError {
36    /// See [`RawHeaderError`].
37    #[snafu(transparent)]
38    RawHeader {
39        /// Source error.
40        source: RawHeaderError,
41    },
42    /// Occurs when the input is too small to fit [`BuildInfo`].
43    #[snafu(display("expected {expected:#x} bytes for build info but had only {actual:#x}:\n{backtrace}"))]
44    DataTooSmall {
45        /// Expected size.
46        expected: usize,
47        /// Actual input size.
48        actual: usize,
49        /// Backtrace to the source of the error.
50        backtrace: Backtrace,
51    },
52    /// Occurs when the input is less aligned than [`BuildInfo`].
53    #[snafu(display("expected {expected}-alignment for build info but got {actual}-alignment:\n{backtrace}"))]
54    Misaligned {
55        /// Expected alignment.
56        expected: usize,
57        /// Actual input alignment.
58        actual: usize,
59        /// Backtrace to the source of the error.
60        backtrace: Backtrace,
61    },
62    /// Occurs when the input does not contain the nitrocode.
63    #[snafu(display("expected nitrocode {expected:#x} at the end of build info but got {actual:#x}:\n{backtrace}"))]
64    NoNitrocode {
65        /// Expected value.
66        expected: u32,
67        /// Actual value.
68        actual: u32,
69        /// Backtrace to the source of the error.
70        backtrace: Backtrace,
71    },
72}
73
74impl BuildInfo {
75    fn check_size(data: &'_ [u8]) -> Result<(), RawBuildInfoError> {
76        let size = size_of::<Self>();
77        if data.len() < size {
78            DataTooSmallSnafu { expected: size, actual: data.len() }.fail()
79        } else {
80            Ok(())
81        }
82    }
83
84    fn handle_pod_cast<T>(result: Result<T, PodCastError>, addr: usize) -> Result<T, RawBuildInfoError> {
85        match result {
86            Ok(build_info) => Ok(build_info),
87            Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) => {
88                MisalignedSnafu { expected: align_of::<Self>(), actual: 1usize << addr.trailing_zeros() }.fail()
89            }
90            Err(PodCastError::AlignmentMismatch) => panic!(),
91            Err(PodCastError::OutputSliceWouldHaveSlop) => panic!(),
92            Err(PodCastError::SizeMismatch) => unreachable!(),
93        }
94    }
95
96    fn check_nitrocode(&self) -> Result<(), RawBuildInfoError> {
97        if self.nitrocode != NITROCODE {
98            NoNitrocodeSnafu { expected: NITROCODE, actual: self.nitrocode }.fail()
99        } else if self.nitrocode_rev != NITROCODE.swap_bytes() {
100            NoNitrocodeSnafu { expected: NITROCODE.swap_bytes(), actual: self.nitrocode_rev }.fail()
101        } else {
102            Ok(())
103        }
104    }
105
106    /// Reinterprets a `&[u8]` as a reference to [`BuildInfo`].
107    ///
108    /// # Errors
109    ///
110    /// This function will return an error if the input is too small, not aligned enough or doesn't contain the nitrocode.
111    pub fn borrow_from_slice(data: &'_ [u8]) -> Result<&'_ Self, RawBuildInfoError> {
112        let size = size_of::<Self>();
113        Self::check_size(data)?;
114        let addr = data as *const [u8] as *const () as usize;
115        let build_info: &Self = Self::handle_pod_cast(bytemuck::try_from_bytes(&data[..size]), addr)?;
116        build_info.check_nitrocode()?;
117        Ok(build_info)
118    }
119
120    /// Reinterprets a `&mut [u8]` as a mutable reference to [`BuildInfo`].
121    ///
122    /// # Errors
123    ///
124    /// This function will return an error if the input is too small, not aligned enough or doesn't contain the nitrocode.
125    pub fn borrow_from_slice_mut(data: &'_ mut [u8]) -> Result<&'_ mut Self, RawBuildInfoError> {
126        let size = size_of::<Self>();
127        Self::check_size(data)?;
128        let addr = data as *const [u8] as *const () as usize;
129        let build_info: &mut Self = Self::handle_pod_cast(bytemuck::try_from_bytes_mut(&mut data[..size]), addr)?;
130        build_info.check_nitrocode()?;
131        Ok(build_info)
132    }
133
134    /// Returns whether this [`BuildInfo`] is compressed.
135    pub fn is_compressed(&self) -> bool {
136        self.compressed_code_end != 0
137    }
138
139    /// Creates a [`DisplayBuildInfo`] which implements [`Display`].
140    pub fn display(&self, indent: usize) -> DisplayBuildInfo<'_> {
141        DisplayBuildInfo { build_info: self, indent }
142    }
143}
144
145/// Can be used to display values inside [`BuildInfo`].
146pub struct DisplayBuildInfo<'a> {
147    build_info: &'a BuildInfo,
148    indent: usize,
149}
150
151impl Display for DisplayBuildInfo<'_> {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        let i = " ".repeat(self.indent);
154        let build_info = &self.build_info;
155        writeln!(f, "{i}Autoload infos start .. : {:#x}", build_info.autoload_infos_start)?;
156        writeln!(f, "{i}Autoload infos end .... : {:#x}", build_info.autoload_infos_end)?;
157        writeln!(f, "{i}Autoload blocks ....... : {:#x}", build_info.autoload_blocks)?;
158        writeln!(f, "{i}.bss start ............ : {:#x}", build_info.bss_start)?;
159        writeln!(f, "{i}.bss end .............. : {:#x}", build_info.bss_end)?;
160        writeln!(f, "{i}Compressed code end ... : {:#x}", build_info.compressed_code_end)?;
161        writeln!(f, "{i}SDK version ........... : {:#x}", build_info.sdk_version)?;
162        Ok(())
163    }
164}