#![warn(missing_docs)]
use binwrite::{BinWrite, WriterOption};
use conv::ValueFrom;
use sha2::{Digest, Sha256};
use std::fmt;
use std::fmt::Formatter;
use std::io::Write;
use thiserror::Error;
#[derive(BinWrite, Debug, Default)]
#[binwrite(little)]
struct BootDatHeader {
inner: BootDatInner,
sha2_hdr: Sha2,
}
#[derive(BinWrite, Debug, Default)]
#[binwrite(little)]
struct BootDatInner {
ident: [u8; 0xc],
vers: [u8; 0x4],
sha2_s2: Sha2,
s2_dst: u32,
s2_size: u32,
s2_enc: u32,
pad: [u8; 0x10],
s3_size: u32,
pad2: Pad2,
}
#[derive(Error, Debug, Clone)]
pub enum Error {
IoError(String),
HashError,
TruncationError,
}
impl std::convert::From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::IoError(err.to_string())
}
}
impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
Error::IoError(s) => write!(fmt, "IO Error: {}", s),
Error::HashError => write!(fmt, "Hash Error"),
Error::TruncationError => write!(fmt, "Number Truncation Error"),
}
}
}
struct Pad2([u8; 0x90]);
impl Default for Pad2 {
fn default() -> Self {
Pad2([0; 0x90])
}
}
impl fmt::Debug for Pad2 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl BinWrite for Pad2 {
fn write_options<W: Write>(
&self,
writer: &mut W,
options: &WriterOption,
) -> std::io::Result<()> {
for item in &self.0 {
BinWrite::write_options(item, writer, options)?;
}
Ok(())
}
}
#[derive(Default)]
struct Sha2([u8; 0x20]);
impl fmt::Debug for Sha2 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl BinWrite for Sha2 {
fn write_options<W: Write>(
&self,
writer: &mut W,
options: &WriterOption,
) -> std::io::Result<()> {
for item in &self.0 {
BinWrite::write_options(item, writer, options)?;
}
Ok(())
}
}
#[must_use]
pub fn get_version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
pub fn generate_boot_dat(payload: &[u8]) -> Result<Vec<u8>, Error> {
let mut header = BootDatHeader::default();
header.inner.ident = [
0x49, 0x6e, 0x73, 0x61, 0x6e, 0x65, 0x20, 0x42, 0x4F, 0x4F, 0x54, 0x00,
];
header.inner.vers = [0x56, 0x31, 0x2E, 0x30];
let stage_2_sha256 = sha256_digest(payload);
header.inner.sha2_s2 = Sha2(stage_2_sha256.try_into().map_err(|_| Error::HashError)?);
header.inner.s2_dst = 0x4001_0000;
header.inner.s2_size = u32::value_from(payload.len()).map_err(|_| Error::TruncationError)?;
let mut inner_serialized = vec![];
header.inner.write(&mut inner_serialized)?;
let header_inner_sha256 = sha256_digest(inner_serialized.as_slice());
header.sha2_hdr = Sha2(
header_inner_sha256
.try_into()
.map_err(|_| Error::HashError)?,
);
let mut serialized = vec![];
header.write(&mut serialized)?;
serialized.extend_from_slice(payload);
Ok(serialized)
}
fn sha256_digest(to_hash: &[u8]) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(to_hash);
hasher.finalize().to_vec()
}
#[cfg(test)]
mod tests {
use hex_literal::hex;
#[test]
fn end_to_end_test() {
let generated = super::generate_boot_dat(&[0xa, 0xb, 0xc]).unwrap();
let hash = super::sha256_digest(generated.as_slice());
assert_eq!(
hash[..],
hex!("6ce4c88e604d351b0e14bca7dbf135b3c8c44428718b704883599f285eed984e")
);
}
}