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
#![deny(missing_docs, missing_debug_implementations)] //! Encodes and decodes Little Fighter 2 (LF2) data files. //! //! # Examples //! //! ## Encode //! //! ```rust,edition2018 //! # use std::io::Error; //! # //! use lf2_codec::DataEncoder; //! //! 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<(), Error> { //! 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 std::io::Error; //! # //! use lf2_codec::DataDecoder; //! //! 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<(), Error> { //! 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::io::{Error, Read}; /// 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>, Error> 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(e) => Err(e), }, ) } } /// 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>, Error> 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(e) => Err(e), }, ) } }