codebreaker 0.3.1

Decrypt & encrypt any cheat code for CodeBreaker PS2
Documentation
//! Encrypt and decrypt cheat codes for all versions of CodeBreaker PS2.
//!
//! Uses [cb1](cb1/index.html) and [cb7](cb7/index.html) under the hood to
//! support both CB v1 and v7 codes.
//!
//! # Quickstart
//!
//! ```
//! use codebreaker::Codebreaker;
//!
//! let input: Vec<(u32, u32)> = vec![
//!     (0x2043AFCC, 0x2411FFFF),
//!     (0x2A973DBD, 0x00000000),
//!     (0xB4336FA9, 0x4DFEFB79),
//!     (0x973E0B2A, 0xA7D4AF10),
//! ];
//! let output: Vec<(u32, u32)> = vec![
//!     (0x2043AFCC, 0x2411FFFF),
//!     (0x201F6024, 0x00000000),
//!     (0xBEEFC0DE, 0x00000000),
//!     (0x2096F5B8, 0x000000BE),
//! ];
//!
//! let mut cb = Codebreaker::new();
//! for (i, code) in input.iter().enumerate() {
//!     assert_eq!(cb.auto_decrypt_code(code.0, code.1), output[i]);
//! }
//! ```

#![deny(clippy::all, clippy::nursery)]
#![deny(nonstandard_style, rust_2018_idioms)]
#![deny(missing_docs, missing_debug_implementations)]
#![forbid(unsafe_code)]
#![no_std]

#[cfg(doctest)]
doc_comment::doctest!("../README.md", readme);

cfg_if::cfg_if! {
    if #[cfg(feature = "std")] {
        extern crate std;
        mod std_alloc {
            pub use std::{
                fmt,
                string::{String, ToString},
                vec,
                vec::Vec,
            };
        }
    } else {
        extern crate alloc;
        mod std_alloc {
            pub use alloc::{
                fmt,
                string::{String, ToString},
                vec,
                vec::Vec,
            };
        }
    }
}

pub mod cb1;
pub mod cb7;
mod rc4;

use cb7::{is_beefcode, Cb7};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Scheme {
    Raw,
    V1,
    V7,
}

/// A processor for CB v1 and v7 codes.
#[derive(Debug, Clone, Copy)]
pub struct Codebreaker {
    scheme: Scheme,
    cb7: Cb7,
    code_lines: usize,
}

/// Does the same as [`new`](#method.new).
impl Default for Codebreaker {
    fn default() -> Self {
        Self::new()
    }
}

impl Codebreaker {
    /// Returns a new processor for encrypting and decrypting a list of CB v1
    /// and v7 codes.
    pub const fn new() -> Self {
        Self {
            scheme: Scheme::Raw,
            cb7: Cb7::new(),
            code_lines: 0,
        }
    }

    /// Returns a new processor for all CB v7 codes published on CMGSCCC.com.
    ///
    /// Lets you omit `B4336FA9 4DFEFB79` as the first code in the list.
    pub fn new_v7() -> Self {
        Self {
            scheme: Scheme::V7,
            cb7: Cb7::default(),
            code_lines: 0,
        }
    }

    /// Encrypts a code and returns the result.
    ///
    /// # Example
    /// ```
    /// use codebreaker::Codebreaker;
    ///
    /// let mut cb = Codebreaker::new();
    /// let code = cb.encrypt_code(0x2043AFCC, 0x2411FFFF);
    /// assert_eq!(code, (0x2AFF014C, 0x2411FFFF));
    /// ```
    pub fn encrypt_code(&mut self, addr: u32, val: u32) -> (u32, u32) {
        let mut code = (addr, val);
        self.encrypt_code_mut(&mut code.0, &mut code.1);
        code
    }

    /// Encrypts a code directly.
    ///
    /// # Example
    /// ```
    /// use codebreaker::Codebreaker;
    ///
    /// let mut cb = Codebreaker::new();
    /// let mut code = (0x2043AFCC, 0x2411FFFF);
    /// cb.encrypt_code_mut(&mut code.0, &mut code.1);
    /// assert_eq!(code, (0x2AFF014C, 0x2411FFFF));
    /// ```
    pub fn encrypt_code_mut(&mut self, addr: &mut u32, val: &mut u32) {
        let (oldaddr, oldval) = (*addr, *val);

        if self.scheme == Scheme::V7 {
            self.cb7.encrypt_code_mut(addr, val);
        } else {
            cb1::encrypt_code_mut(addr, val);
        }

        if is_beefcode(oldaddr) {
            self.cb7.beefcode(oldaddr, oldval);
            self.scheme = Scheme::V7;
        }
    }

    /// Decrypts a code and returns the result.
    ///
    /// # Example
    /// ```
    /// use codebreaker::Codebreaker;
    ///
    /// let encrypted: Vec<(u32, u32)> = vec![
    ///     (0x2AFF014C, 0x2411FFFF),
    ///     (0xB4336FA9, 0x4DFEFB79),
    ///     (0x973E0B2A, 0xA7D4AF10),
    /// ];
    /// let decrypted: Vec<(u32, u32)> = vec![
    ///     (0x2043AFCC, 0x2411FFFF),
    ///     (0xBEEFC0DE, 0x00000000),
    ///     (0x2096F5B8, 0x000000BE),
    /// ];
    ///
    /// let mut cb = Codebreaker::new();
    /// for (i, code) in encrypted.iter().enumerate() {
    ///     let result = cb.decrypt_code(code.0, code.1);
    ///     assert_eq!(result, decrypted[i]);
    /// }
    /// ```
    pub fn decrypt_code(&mut self, addr: u32, val: u32) -> (u32, u32) {
        let mut code = (addr, val);
        self.decrypt_code_mut(&mut code.0, &mut code.1);
        code
    }

    /// Decrypts a code directly.
    ///
    /// # Example
    /// ```
    /// use codebreaker::Codebreaker;
    ///
    /// let mut encrypted: Vec<(u32, u32)> = vec![
    ///     (0x2AFF014C, 0x2411FFFF),
    ///     (0xB4336FA9, 0x4DFEFB79),
    ///     (0x973E0B2A, 0xA7D4AF10),
    /// ];
    /// let decrypted: Vec<(u32, u32)> = vec![
    ///     (0x2043AFCC, 0x2411FFFF),
    ///     (0xBEEFC0DE, 0x00000000),
    ///     (0x2096F5B8, 0x000000BE),
    /// ];
    ///
    /// let mut cb = Codebreaker::new();
    /// for code in encrypted.iter_mut() {
    ///     cb.decrypt_code_mut(&mut code.0, &mut code.1);
    /// }
    /// assert_eq!(encrypted, decrypted);
    /// ```
    pub fn decrypt_code_mut(&mut self, addr: &mut u32, val: &mut u32) {
        if self.scheme == Scheme::V7 {
            self.cb7.decrypt_code_mut(addr, val);
        } else {
            cb1::decrypt_code_mut(addr, val);
        }

        if is_beefcode(*addr) {
            self.cb7.beefcode(*addr, *val);
            self.scheme = Scheme::V7;
        }
    }

    /// Smart version of [`decrypt_code`](#method.decrypt_code) that detects if
    /// and how a code needs to be decrypted.
    ///
    /// # Example
    /// ```
    /// use codebreaker::Codebreaker;
    ///
    /// let input: Vec<(u32, u32)> = vec![
    ///     (0x2043AFCC, 0x2411FFFF),
    ///     (0x2A973DBD, 0x00000000),
    ///     (0xB4336FA9, 0x4DFEFB79),
    ///     (0x973E0B2A, 0xA7D4AF10),
    /// ];
    /// let output: Vec<(u32, u32)> = vec![
    ///     (0x2043AFCC, 0x2411FFFF),
    ///     (0x201F6024, 0x00000000),
    ///     (0xBEEFC0DE, 0x00000000),
    ///     (0x2096F5B8, 0x000000BE),
    /// ];
    ///
    /// let mut cb = Codebreaker::new();
    /// for (i, code) in input.iter().enumerate() {
    ///     assert_eq!(cb.auto_decrypt_code(code.0, code.1), output[i]);
    /// }
    /// ```
    pub fn auto_decrypt_code(&mut self, addr: u32, val: u32) -> (u32, u32) {
        let mut code = (addr, val);
        self.auto_decrypt_code_mut(&mut code.0, &mut code.1);
        code
    }

    /// Smart version of [`decrypt_code_mut`](#method.decrypt_code_mut) that
    /// detects if and how a code needs to be decrypted.
    pub fn auto_decrypt_code_mut(&mut self, addr: &mut u32, val: &mut u32) {
        if self.scheme != Scheme::V7 {
            if self.code_lines == 0 {
                self.code_lines = num_code_lines(*addr);
                if (*addr >> 24) & 0x0e != 0 {
                    if is_beefcode(*addr) {
                        // ignore raw beefcode
                        self.code_lines -= 1;
                        return;
                    }
                    self.scheme = Scheme::V1;
                    self.code_lines -= 1;
                    cb1::decrypt_code_mut(addr, val);
                } else {
                    self.scheme = Scheme::Raw;
                    self.code_lines -= 1;
                }
            } else {
                self.code_lines -= 1;
                if self.scheme == Scheme::Raw {
                    return;
                }
                cb1::decrypt_code_mut(addr, val);
            }
        } else {
            self.cb7.decrypt_code_mut(addr, val);
            if self.code_lines == 0 {
                self.code_lines = num_code_lines(*addr);
                if self.code_lines == 1 && *addr == 0xffff_ffff {
                    // XXX: changing encryption via "FFFFFFFF 000xnnnn" is not supported
                    self.code_lines = 0;
                    return;
                }
            }
            self.code_lines -= 1;
        }

        if is_beefcode(*addr) {
            self.cb7.beefcode(*addr, *val);
            self.scheme = Scheme::V7;
            self.code_lines = 1;
        }
    }
}

const fn num_code_lines(addr: u32) -> usize {
    let cmd = addr >> 28;

    if cmd < 3 || cmd > 6 {
        1
    } else if cmd == 3 {
        if addr & 0x0040_0000 != 0 {
            2
        } else {
            1
        }
    } else {
        2
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::code::Code;
    use crate::std_alloc::{vec, Vec};
    #[cfg(feature = "std")]
    use pretty_assertions::assert_eq;

    struct Test {
        cb: Codebreaker,
        decrypted: Vec<Code>,
        encrypted: Vec<Code>,
    }

    fn tests() -> Vec<Test> {
        vec![
            Test {
                cb: Codebreaker::new(),
                decrypted: vec![
                    "2043AFCC 2411FFFF".into(),
                    "BEEFC0DE 00000000".into(),
                    "2096F5B8 000000BE".into(),
                ],
                encrypted: vec![
                    "2AFF014C 2411FFFF".into(),
                    "B4336FA9 4DFEFB79".into(),
                    "973E0B2A A7D4AF10".into(),
                ],
            },
            Test {
                cb: Codebreaker::new_v7(),
                decrypted: vec![
                    "9029BEAC 0C0A9225".into(),
                    "201F6024 00000000".into(),
                    "2096F5B8 000000BE".into(),
                ],
                encrypted: vec![
                    "D08F3A49 00078A53".into(),
                    "3818DDE5 E72B2B16".into(),
                    "973E0B2A A7D4AF10".into(),
                ],
            },
            Test {
                cb: Codebreaker::default(),
                decrypted: vec![
                    "9029BEAC 0C0A9225".into(),
                    "201F6024 00000000".into(),
                    "2096F5B8 000000BE".into(),
                ],
                encrypted: vec![
                    "9A545CC6 188CBCFB".into(),
                    "2A973DBD 00000000".into(),
                    "2A03B60A 000000BE".into(),
                ],
            },
        ]
    }

    #[test]
    fn test_encrypt_code() {
        for t in &mut tests() {
            for (i, &code) in t.decrypted.iter().enumerate() {
                let result: Code = t.cb.encrypt_code(code.0, code.1).into();
                assert_eq!(result, t.encrypted[i]);
            }
        }
    }

    #[test]
    fn test_encrypt_code_mut() {
        for t in &mut tests() {
            for (i, code) in t.decrypted.iter_mut().enumerate() {
                t.cb.encrypt_code_mut(&mut code.0, &mut code.1);
                assert_eq!(*code, t.encrypted[i]);
            }
        }
    }

    #[test]
    fn test_decrypt_code() {
        for t in &mut tests() {
            for (i, &code) in t.encrypted.iter().enumerate() {
                let result: Code = t.cb.decrypt_code(code.0, code.1).into();
                assert_eq!(result, t.decrypted[i]);
            }
        }
    }

    #[test]
    fn test_decrypt_code_mut() {
        for t in &mut tests() {
            for (i, code) in t.encrypted.iter_mut().enumerate() {
                t.cb.decrypt_code_mut(&mut code.0, &mut code.1);
                assert_eq!(*code, t.decrypted[i]);
            }
        }
    }

    struct AutoTest {
        input: Vec<Code>,
        output: Vec<Code>,
    }

    fn auto_tests() -> Vec<AutoTest> {
        vec![
            AutoTest {
                // raw
                input: vec![
                    "9029BEAC 0C0A9225".into(),
                    "201F6024 00000000".into(),
                    "2096F5B8 000000BE".into(),
                ],
                output: vec![
                    "9029BEAC 0C0A9225".into(),
                    "201F6024 00000000".into(),
                    "2096F5B8 000000BE".into(),
                ],
            },
            AutoTest {
                // v1 encrypted
                input: vec![
                    "9A545CC6 188CBCFB".into(),
                    "2A973DBD 00000000".into(),
                    "2A03B60A 000000BE".into(),
                ],
                output: vec![
                    "9029BEAC 0C0A9225".into(),
                    "201F6024 00000000".into(),
                    "2096F5B8 000000BE".into(),
                ],
            },
            AutoTest {
                // v7 encrypted
                input: vec![
                    "B4336FA9 4DFEFB79".into(),
                    "D08F3A49 00078A53".into(),
                    "3818DDE5 E72B2B16".into(),
                    "973E0B2A A7D4AF10".into(),
                ],
                output: vec![
                    "BEEFC0DE 00000000".into(),
                    "9029BEAC 0C0A9225".into(),
                    "201F6024 00000000".into(),
                    "2096F5B8 000000BE".into(),
                ],
            },
            AutoTest {
                // v1 and v7 encrypted
                input: vec![
                    "9A545CC6 188CBCFB".into(),
                    "2A973DBD 00000000".into(),
                    "B4336FA9 4DFEFB79".into(),
                    "973E0B2A A7D4AF10".into(),
                ],
                output: vec![
                    "9029BEAC 0C0A9225".into(),
                    "201F6024 00000000".into(),
                    "BEEFC0DE 00000000".into(),
                    "2096F5B8 000000BE".into(),
                ],
            },
            AutoTest {
                // raw, v1, and v7 encrypted
                input: vec![
                    "9029BEAC 0C0A9225".into(),
                    "2A973DBD 00000000".into(),
                    "B4336FA9 4DFEFB79".into(),
                    "973E0B2A A7D4AF10".into(),
                ],
                output: vec![
                    "9029BEAC 0C0A9225".into(),
                    "201F6024 00000000".into(),
                    "BEEFC0DE 00000000".into(),
                    "2096F5B8 000000BE".into(),
                ],
            },
        ]
    }

    #[test]
    fn test_auto_decrypt_code() {
        for t in &mut auto_tests() {
            let mut cb = Codebreaker::new();
            for (i, &code) in t.input.iter().enumerate() {
                let result: Code = cb.auto_decrypt_code(code.0, code.1).into();
                assert_eq!(result, t.output[i]);
            }
        }
    }

    #[test]
    fn test_auto_decrypt_code_mut() {
        for t in &mut auto_tests() {
            let mut cb = Codebreaker::new();
            for (i, code) in t.input.iter_mut().enumerate() {
                cb.auto_decrypt_code_mut(&mut code.0, &mut code.1);
                assert_eq!(*code, t.output[i]);
            }
        }
    }
}

#[cfg(test)]
mod code {
    use crate::std_alloc::{fmt, Vec};

    #[derive(Copy, Clone, PartialEq, Eq)]
    pub struct Code(pub u32, pub u32);

    impl From<(u32, u32)> for Code {
        fn from(t: (u32, u32)) -> Self {
            Self(t.0, t.1)
        }
    }

    impl From<&str> for Code {
        fn from(s: &str) -> Self {
            let t: Vec<u32> = s
                .splitn(2, ' ')
                .map(|v| u32::from_str_radix(v, 16).expect("invalid code format"))
                .collect();

            Self(t[0], t[1])
        }
    }

    // Implements ToString
    impl fmt::Display for Code {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "{:08X} {:08X}", self.0, self.1)
        }
    }

    // Used by assert_eq!
    impl fmt::Debug for Code {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "{}", self)
        }
    }
}