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
#![deny(missing_docs, missing_debug_implementations)]

//! Encodes and decodes Little Fighter 2 (LF2) data files.
//!
//! # Examples
//!
//! ## Encode
//!
//! ```rust,edition2018
//! use lf2_codec::{DataEncoder, EncodeError};
//!
//! const CHARACTER_DAT_ENCODED: &[u8] = b"\
//!   This is sample data as bytes. \
//!   The first 123 bytes are ignored during decoding, the rest are decoded using a caesar cipher. \
//!   \xab\xc6\xaf\xd5\xc0\xd4\xa7\xcc\xcc\xcf\xb3\
//!   \xe1\xc6\xb5\xca\x83\x93\x97\xdf\xe4\xe2\xac\
//!   \xdb\xab\xc6\xc0\xd9\xd4\xad\xe3\xd2\xa5";
//!
//! # fn main() -> Result<(), EncodeError> {
//! let data = "<bmp_begin>name: Azriel<bmp_end>";
//!
//! let encoded = DataEncoder::encode(data.as_bytes())?;
//!
//! assert_eq!(CHARACTER_DAT_ENCODED, encoded);
//! #
//! # Ok(())
//! # }
//! ```
//!
//! ## Decode
//!
//! ```rust,edition2018
//! use lf2_codec::{DataDecoder, DecodeError};
//!
//! const CHARACTER_DAT_ENCODED: &[u8] = b"\
//!   This is sample data as bytes. \
//!   The first 123 bytes are ignored during decoding, the rest are decoded using a caesar cipher. \
//!   \xab\xc6\xaf\xd5\xc0\xd4\xa7\xcc\xcc\xcf\xb3\
//!   \xe1\xc6\xb5\xca\x83\x93\x97\xdf\xe4\xe2\xac\
//!   \xdb\xab\xc6\xc0\xd9\xd4\xad\xe3\xd2\xa5";
//!
//! # fn main() -> Result<(), DecodeError> {
//! let decoded = DataDecoder::decode(CHARACTER_DAT_ENCODED)?;
//!
//! let expected = "<bmp_begin>name: Azriel<bmp_end>";
//!
//! assert_eq!(expected, String::from_utf8_lossy(&decoded));
//! #
//! # Ok(())
//! # }
//! ```

use std::{
    fs::File,
    io::{BufReader, Read},
    path::Path,
};

pub use crate::{decode_error::DecodeError, encode_error::EncodeError, error::Error};

mod decode_error;
mod encode_error;
mod error;

/// Key used to shift the ascii code of each object.
pub const CAESAR_CIPHER: &[u8] = b"odBearBecauseHeIsVeryGoodSiuHungIsAGo";

/// Data used to fill the first 123 bytes of the data file. Strictly 123 bytes long.
pub const DATA_HEADER: &[u8; 123] = b"This is sample data as bytes. \
    The first 123 bytes are ignored during decoding, the rest are decoded using a caesar cipher. ";

/// Default number of bytes to allocate for the final result.
const DATA_CAPACITY_DEFAULT: usize = 1024;

/// Encodes data that the LF2 application may read.
#[derive(Debug)]
pub struct DataEncoder;

impl DataEncoder {
    /// Encodes object data for the LF2 application.
    ///
    /// # Parameters
    ///
    /// * `stream`: The stream of object data to encode.
    pub fn encode<R>(stream: R) -> Result<Vec<u8>, EncodeError>
    where
        R: Read,
    {
        let mut encoded = Vec::with_capacity(DATA_CAPACITY_DEFAULT);
        encoded.extend(DATA_HEADER.iter());

        let bytes = stream.bytes();
        bytes.zip(CAESAR_CIPHER.iter().copied().cycle()).try_fold(
            encoded,
            |mut encoded, (byte_result, cipher_byte)| match byte_result {
                Ok(byte) => {
                    let encoded_byte = byte.wrapping_add(cipher_byte);
                    encoded.push(encoded_byte);
                    Ok(encoded)
                }
                Err(error) => Err(EncodeError { error }),
            },
        )
    }
}

/// Decodes object data from LF2.
#[derive(Debug)]
pub struct DataDecoder;

impl DataDecoder {
    /// Decodes LF2 object data.
    ///
    /// # Parameters
    ///
    /// * `stream`: The stream of encoded object data.
    pub fn decode<R>(stream: R) -> Result<Vec<u8>, DecodeError>
    where
        R: Read,
    {
        let bytes = stream.bytes();

        bytes
            .skip(123)
            .zip(CAESAR_CIPHER.iter().copied().cycle())
            .try_fold(
                Vec::with_capacity(DATA_CAPACITY_DEFAULT),
                |mut decoded, (byte_result, cipher_byte)| match byte_result {
                    Ok(byte) => {
                        let decoded_byte = byte.wrapping_sub(cipher_byte);
                        decoded.push(decoded_byte);
                        Ok(decoded)
                    }
                    Err(error) => Err(DecodeError { error }),
                },
            )
    }

    /// Decodes LF2 object data from a file path.
    ///
    /// # Parameters
    ///
    /// * `path`: Path to the file to decode.
    pub fn decode_path<P>(path: P) -> Result<Vec<u8>, DecodeError>
    where
        P: AsRef<Path>,
    {
        let path = AsRef::<Path>::as_ref(&path);
        let file = File::open(path)?;
        let buf_reader = BufReader::new(file);

        Self::decode(buf_reader)
    }
}