Skip to content

Commit

Permalink
Introduce Ipl3ChecksumError
Browse files Browse the repository at this point in the history
  • Loading branch information
AngheloAlf committed Dec 16, 2023
1 parent 5d005ea commit f595339
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 45 deletions.
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ crate-type = ["lib", "cdylib"]
[dependencies]
md5 = "0.7.0"
pyo3 = { version="0.20.0", features = ["extension-module"], optional = true }
# thiserror = "1.0"

# [dev-dependencies]
# rstest = "0.18.2"
thiserror = "1.0.51"

[features]
c_bindings = []
Expand Down
2 changes: 1 addition & 1 deletion src/ipl3checksum/detect.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ from .cickinds import CICKind
def detectCICRaw(rawBytes: bytes) -> CICKind|None:
"""Tries to detect an IPL3 binary.
The argument to this function must be exactly the IPL3 binary, stripping the rest of the ROM.
The argument to this function must be exactly the IPL3 binary.
Args:
rawBytes (bytes): IPL3 binary in big endian format.
Expand Down
40 changes: 25 additions & 15 deletions src/rs/checksum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* SPDX-License-Identifier: MIT */

use crate::cickinds::CICKind;
use crate::{detect, utils};
use crate::{detect, error::Ipl3ChecksumError, utils};

fn read_word_from_ram(rom_words: &[u32], entrypoint_ram: u32, ram_addr: u32) -> u32 {
rom_words[((ram_addr - entrypoint_ram + 0x1000) / 4) as usize]
Expand Down Expand Up @@ -31,12 +31,12 @@ fn read_word_from_ram(rom_words: &[u32], entrypoint_ram: u32, ram_addr: u32) ->
/// let checksum = ipl3checksum::calculate_checksum(&bytes, &kind).unwrap();
/// println!("{:08X} {:08X}", checksum.0, checksum.1);
/// ```
pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32)> {
pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Result<(u32, u32), Ipl3ChecksumError> {
if rom_bytes.len() < 0x101000 {
return None;
return Err(Ipl3ChecksumError::BufferNotBigEnough{buffer_len: rom_bytes.len(), expected_len: 0x101000});
}

let rom_words = utils::read_u32_vec(rom_bytes, 0, 0x101000 / 4);
let rom_words = utils::read_u32_vec(rom_bytes, 0, 0x101000 / 4)?;

let seed = kind.get_seed();
let magic = kind.get_magic();
Expand Down Expand Up @@ -197,7 +197,7 @@ pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32)
s0 = t8 ^ t4;
}

Some((a3, s0))
Ok((a3, s0))
}

/// Calculates the checksum required by an official CIC of a N64 ROM.
Expand All @@ -223,9 +223,9 @@ pub fn calculate_checksum(rom_bytes: &[u8], kind: &CICKind) -> Option<(u32, u32)
/// let bytes = vec![0; 0x101000];
/// let checksum = ipl3checksum::calculate_checksum_autodetect(&bytes);
/// /* This will return `None` because there's no ipl3 binary on an array of zeroes */
/// assert!(checksum.is_none());
/// assert!(checksum.is_err());
/// ```
pub fn calculate_checksum_autodetect(rom_bytes: &[u8]) -> Option<(u32, u32)> {
pub fn calculate_checksum_autodetect(rom_bytes: &[u8]) -> Result<(u32, u32), Ipl3ChecksumError> {
let kind = detect::detect_cic(rom_bytes)?;

calculate_checksum(rom_bytes, &kind)
Expand All @@ -237,9 +237,7 @@ mod tests {
use std::fs;

#[test]
fn test_dummy_files() -> Result<(), ()> {
println!("asdf");

fn test_dummy_files() -> Result<(), super::Ipl3ChecksumError> {
for path_result in fs::read_dir("tests/dummytests").unwrap() {
let ipl3_folder = path_result.unwrap();
let folder_name = ipl3_folder.file_name();
Expand Down Expand Up @@ -275,7 +273,7 @@ mod tests {
);

println!(" Checking checksum...");
let bin_checksum = utils::read_u32_vec(&bin_bytes, 0x10, 2);
let bin_checksum = utils::read_u32_vec(&bin_bytes, 0x10, 2)?;

println!(
" Expected checksum is: 0x{:08X} 0x{:08X}",
Expand All @@ -302,12 +300,24 @@ pub(crate) mod python_bindings {
use pyo3::prelude::*;

#[pyfunction]
pub(crate) fn calculateChecksum(rom_bytes: &[u8], kind: &super::CICKind) -> Option<(u32, u32)> {
super::calculate_checksum(rom_bytes, kind)
pub(crate) fn calculateChecksum(rom_bytes: &[u8], kind: &super::CICKind) -> Result<Option<(u32, u32)>, super::Ipl3ChecksumError> {
match super::calculate_checksum(rom_bytes, kind) {
Ok(checksum) => Ok(Some(checksum)),
Err(e) => match e {
super::Ipl3ChecksumError::BufferNotBigEnough { buffer_len, expected_len } => Ok(None),
_ => Err(e), // To trigger an exception on Python's side
},
}
}

#[pyfunction]
pub(crate) fn calculateChecksumAutodetect(rom_bytes: &[u8]) -> Option<(u32, u32)> {
super::calculate_checksum_autodetect(rom_bytes)
pub(crate) fn calculateChecksumAutodetect(rom_bytes: &[u8]) -> Result<Option<(u32, u32)>, super::Ipl3ChecksumError> {
match super::calculate_checksum_autodetect(rom_bytes) {
Ok(checksum) => Ok(Some(checksum)),
Err(e) => match e {
super::Ipl3ChecksumError::BufferNotBigEnough { buffer_len, expected_len } => Ok(None),
_ => Err(e), // To trigger an exception on Python's side
},
}
}
}
41 changes: 29 additions & 12 deletions src/rs/detect.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
/* SPDX-FileCopyrightText: © 2023 Decompollaborate */
/* SPDX-License-Identifier: MIT */

use crate::cickinds::CICKind;
use crate::utils;
use crate::{cickinds::CICKind, error::Ipl3ChecksumError, utils};

/// Tries to detect an IPL3 binary.
///
/// The argument to this function must be exactly the IPL3 binary, stripping the rest of the ROM.
/// The argument to this function must be exactly the IPL3 binary.
///
/// ## Arguments
///
/// * `raw_bytes` - IPL3 binary in big endian format.
///
/// ## Return
/// * The detected CIC kind, or `None` if was not able to detect the CIC kind.
pub fn detect_cic_raw(raw_bytes: &[u8]) -> Option<CICKind> {
///
/// * The detected CIC kind, or `Ipl3ChecksumError` if was not able to detect the CIC kind.
pub fn detect_cic_raw(raw_bytes: &[u8]) -> Result<CICKind, Ipl3ChecksumError> {
if raw_bytes.len() != 0xFC0 {
return None;
return Err(Ipl3ChecksumError::BufferSizeIsWrong{buffer_len:raw_bytes.len() , expected_len: 0xFC0});
}

let bytes_hash = utils::get_hash_md5(raw_bytes);

CICKind::from_hash_md5(&bytes_hash)
match CICKind::from_hash_md5(&bytes_hash) {
Some(cic) => Ok(cic),
None => Err(Ipl3ChecksumError::UnableToDetectCIC{hash: bytes_hash}),
}
}

/// Tries to detect an IPL3 in a ROM.
Expand All @@ -35,7 +38,7 @@ pub fn detect_cic_raw(raw_bytes: &[u8]) -> Option<CICKind> {
/// ## Return
///
/// * The detected CIC kind, or `None` if was not able to detect the CIC kind.
pub fn detect_cic(rom_bytes: &[u8]) -> Option<CICKind> {
pub fn detect_cic(rom_bytes: &[u8]) -> Result<CICKind, Ipl3ChecksumError> {
detect_cic_raw(&rom_bytes[0x40..0x1000])
}

Expand All @@ -45,12 +48,26 @@ pub(crate) mod python_bindings {
use pyo3::prelude::*;

#[pyfunction]
pub(crate) fn detectCICRaw(raw_bytes: &[u8]) -> Option<super::CICKind> {
super::detect_cic_raw(raw_bytes)
pub(crate) fn detectCICRaw(raw_bytes: &[u8]) -> Result<Option<super::CICKind>, super::Ipl3ChecksumError> {
match super::detect_cic_raw(raw_bytes) {
Ok(cic) => Ok(Some(cic)),
Err(e) => match e {
super::Ipl3ChecksumError::BufferSizeIsWrong { buffer_len, expected_len } => Ok(None),
super::Ipl3ChecksumError::UnableToDetectCIC { hash } => Ok(None),
_ => Err(e), // To trigger an exception on Python's side
},
}
}

#[pyfunction]
pub(crate) fn detectCIC(rom_bytes: &[u8]) -> Option<super::CICKind> {
super::detect_cic(rom_bytes)
pub(crate) fn detectCIC(rom_bytes: &[u8]) -> Result<Option<super::CICKind>, super::Ipl3ChecksumError> {
match super::detect_cic(rom_bytes) {
Ok(cic) => Ok(Some(cic)),
Err(e) => match e {
super::Ipl3ChecksumError::BufferSizeIsWrong { buffer_len, expected_len } => Ok(None),
super::Ipl3ChecksumError::UnableToDetectCIC { hash } => Ok(None),
_ => Err(e), // To trigger an exception on Python's side
},
}
}
}
38 changes: 38 additions & 0 deletions src/rs/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* SPDX-FileCopyrightText: © 2023 Decompollaborate */
/* SPDX-License-Identifier: MIT */

use thiserror;

#[cfg(feature = "python_bindings")]
use pyo3::exceptions::PyRuntimeError;
#[cfg(feature = "python_bindings")]
use pyo3::prelude::*;

/* This needs to be in sync with the C equivalent at `crunch64_error.h` */
#[cfg_attr(feature = "c_bindings", repr(u32))]
#[derive(Clone, Debug, PartialEq, Eq, Hash, thiserror::Error)]
pub enum Ipl3ChecksumError {
#[error("Not an error")]
Okay,
#[error("Unaligned read at offset 0x{offset:X}")]
UnalignedRead{ offset: usize },
#[error("Failed to convert bytes at offset 0x{offset:X}")]
ByteConversion{ offset: usize },
#[error("Tried to access data out of bounds at offset 0x{offset:X}. Requested bytes: 0x{requested_bytes:X}. Buffer length: 0x{buffer_len:X}")]
OutOfBounds{offset: usize, requested_bytes: usize, buffer_len: usize},
#[error("Pointer is null")]
NullPointer,
#[error("The input byte buffer is not big enough. It should be at least 0x{expected_len:X} bytes long, but it was 0x{buffer_len:X} bytes")]
BufferNotBigEnough{ buffer_len: usize, expected_len: usize },
#[error("The input byte buffer didn't have the expected size. It should be exactly 0x{expected_len:X} bytes long, but it was 0x{buffer_len:X} bytes")]
BufferSizeIsWrong{ buffer_len: usize, expected_len: usize },
#[error("Unable to detect the CIC variant because the computed hash did not match any of the known variants. Computed hash: {hash}")]
UnableToDetectCIC{ hash: String },
}

#[cfg(feature = "python_bindings")]
impl std::convert::From<Ipl3ChecksumError> for PyErr {
fn from(err: Ipl3ChecksumError) -> PyErr {
PyRuntimeError::new_err(err.to_string())
}
}
3 changes: 2 additions & 1 deletion src/rs/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
mod checksum;
mod cickinds;
mod detect;

mod error;
mod utils;

pub use checksum::*;
pub use cickinds::*;
pub use detect::*;
pub use error::*;

#[cfg(feature = "python_bindings")]
use pyo3::prelude::*;
Expand Down
22 changes: 10 additions & 12 deletions src/rs/utils.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
/* SPDX-FileCopyrightText: © 2023 Decompollaborate */
/* SPDX-License-Identifier: MIT */

pub(crate) fn read_u32(bytes: &[u8], offset: usize) -> u32 {
use crate::error::Ipl3ChecksumError;

pub(crate) fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, Ipl3ChecksumError> {
if offset % 4 != 0 {
panic!("Unaligned read");
return Err(Ipl3ChecksumError::UnalignedRead{ offset });
}

if offset + 4 > bytes.len() {
panic!("Out of bounds. Offset {:X}, len {:X}", offset, bytes.len());
return Err(Ipl3ChecksumError::OutOfBounds{offset, requested_bytes: 4, buffer_len: bytes.len()});
}

/*
match bytes[offset..offset + 4].try_into() {
Ok(bytes) => u32::from_be_bytes(bytes),
Err(_error) => todo!(),
Ok(bytes) => Ok(u32::from_be_bytes(bytes)),
Err(_error) => Err(Ipl3ChecksumError::ByteConversion{offset}),
}
*/

u32::from_be_bytes(bytes[offset..offset + 4].try_into().unwrap())
}

pub(crate) fn read_u32_vec(bytes: &[u8], offset: usize, len: usize) -> Vec<u32> {
pub(crate) fn read_u32_vec(bytes: &[u8], offset: usize, len: usize) -> Result<Vec<u32>, Ipl3ChecksumError> {
let mut ret = Vec::with_capacity(len);

for i in 0..len {
ret.push(read_u32(bytes, offset + i * 4));
ret.push(read_u32(bytes, offset + i * 4).unwrap());
}

ret
Ok(ret)
}

pub(crate) fn get_hash_md5(bytes: &[u8]) -> String {
Expand Down

0 comments on commit f595339

Please sign in to comment.